Skip to content

Commit a877fab

Browse files
committed
Adding documentation for Shared/Sink/Source semantics.
1 parent 4dd54bb commit a877fab

File tree

2 files changed

+74
-0
lines changed

2 files changed

+74
-0
lines changed

docs/02-data-exchange.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<img src="https://content.arduino.cc/website/Arduino_logo_teal.svg" height="100" align="right"/>
2+
3+
Data Exchange between Threads
4+
=============================
5+
## Introduction
6+
When a Arduino sketch formerly consisting of a single `loop()` is split into multiple `loop()` functions contained in multiple `*.inot` files it becomes necessary to device a thead-safe mechanism to exchange data between those threads. `Arduino_Threads` supports two different semantics for data exchange between threads, `Shared` variables and `Sink`/`Source` semantic.
7+
8+
## `Shared`
9+
A `Shared` variable is a global variable accessible to all threads. It is declared within `SharedVariables.h` which is automatically included on the top of each `*.inot`-file. Shared variables are declared using the `SHARED` macro in the following way:
10+
```C++
11+
/* SharedVariables.h */
12+
SHARED(counter, int); /* A globally available, threadsafe, shared variable of type 'int'. */
13+
```
14+
A shared variable can contain up to `SHARED_QUEUE_SIZE` entries and semantically represents a FIFO ([First-In/First-Out](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics))) queue. New values can be inserted into the queue by naturally using the assignment operator `=` as if it was just any ordinary [plain-old-data](https://en.wikipedia.org/wiki/Passive_data_structure) (POD) type, i.e. `int`, `char`, ...
15+
```C++
16+
/* Thread_1.inot */
17+
counter = 10; /* Store a value into the shared variable in a threadsafe manner. */
18+
```
19+
If the internal queue is full the oldest element is discarded and the latest element is inserted into the queue.
20+
21+
Retrieving stored data works also very naturally like it would for any POD data type:
22+
```C++
23+
/* Thread_2.inot */
24+
Serial.println(counter); /* Retrieves a value from the shared variable in a threadsafe manner. */
25+
```
26+
Should the internal queue be empty when trying to read the latest available value then the thread reading the shared variable will be suspended and the next available thread will be schedulded. Once a new value is stored inside the shared variable the suspended thread resumes operation and consumes the value which has been stored in the internal queue.
27+
28+
Since shared variables are globally accessible from every thread each thread can read from or write to the shared variable. The user is responsible for using the shared variable in a responsible and sensible way, i.e. reading a shared variable from different threads is generally a bad idea, as on every read a item is removed from the queue within the shared variable and other threads can't access the read value anymore.
29+
30+
## `Sink`/`Source`
31+
The idea behind the `Sink`/`Source` semantics is to model data exchange between one data producer (`Source`) and zero, one or multiple data consumers (`Sink`). A data producer or `Source` can be declared in any `*.ino` or `*.inot`-file using the `SOURCE` macro:
32+
```C++
33+
/* DataProducerThread.inot */
34+
SOURCE(counter, int); /* Declaration of a data source of type `int`. */
35+
```
36+
In a similar way, a data consumer can be declared in any `*.ino` or `*.inot`-file using the `SINK` macro. In difference to `Shared` where the size of the internal queue is globally set for all shared variables you can define your desired internal buffer size separately for each `Sink`.
37+
```C++
38+
/* DataConsumerThread_1.inot */
39+
SINK(counter, int); /* Declaration of a data sink of type `int` with a internal queue size of '1'. */
40+
```
41+
```C++
42+
/* DataConsumerThread_2.inot */
43+
SINK(counter, int, 10); /* Declaration of a data sink of type `int` with a internal queue size of '10'. */
44+
```
45+
In order to actually facilitate the flow of data from a source to a sink the sinks must be connected to the desired data source. This is done within the main `ino`-file:
46+
```C++
47+
/* MySinkSourceDemo.ino */
48+
DataProducerThread.counter.connectTo(DataConsumerThread_1.counter);
49+
DataProducerThread.counter.connectTo(DataConsumerThread_2.counter);
50+
```
51+
Whenever a new value is assigned to a data source, i.e.
52+
```C++
53+
/* DataProducerThread.inot */
54+
counter = 10;
55+
```
56+
data is automatically copied and stored within the internal queues of all connected data sinks, from where it can be retrieved, i.e.
57+
```C++
58+
/* DataConsumerThread_1.inot */
59+
Serial.println(counter);
60+
```
61+
```C++
62+
/* DataConsumerThread_2.inot */
63+
Serial.println(counter);
64+
```
65+
If a thread tries to read from an empty `Sink` the thread is suspended and the next ready thread is scheduled. When a new value is written to a `Source` and consequently copied to a `Sink` the suspended thread is resumed and continuous execution (i.e. read the data and act upon it).
66+
67+
Since data is actually copied multiple threads can read data from a single source without data being lost. This is an advantage compared to a simple shared variable. Furthermore you can not write to `Sink` or read from a `Source` - attempting to do so results in a compilation error.
68+
69+
## Comparison
70+
| | :+1: | :-1: |
71+
|:---:|:---:|:---:|
72+
| `Shared` | :+1: Needs only to declared once (`SharedVariables.h`). | :-1: Basically a global variable, with all the disadvantages those entail.<br/> :-1: Size of internal queue fixed for ALL shared variables.<br/> :-1: No protection against misuse (i.e. reading from multiple threads).<br/> |
73+
| `Sink`/`Source` | :+1: Define internal queue size separately for each `Sink`.<br/> :+1: Supports multiple data consumers for a single data producer.<br/> :+1: Read/Write protection - Can't read from `Source`, can't write to `Sink`.<br/> :+1: Mandatory connecting (plumbing) within main `*.ino`-file makes data flows easily visible.<br/> | :-1: Needs manual connection (plumbing) to connect `Sink`'s to `Source`'s. |

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ Threading on Arduino can be achieved by leveraging the [Arduino_Threads](https:/
1313

1414
### Table of Contents
1515
* [Threading Basics](01-threading-basics.md)
16+
* [Data exchange between threads](02-data-exchange.md)

0 commit comments

Comments
 (0)