Skip to content

Commit a7511b1

Browse files
committed
#5 - Moved to Nested Namespaces
#4 - Made a good start on the README.MD
1 parent 257e997 commit a7511b1

File tree

7 files changed

+854
-583
lines changed

7 files changed

+854
-583
lines changed

README.md

Lines changed: 253 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,255 @@
1-
# ESPressio-Threads
2-
Threading Components of the Flowduino ESPressio Development Platform
1+
# ESPressio Threads
2+
Threading Components of the Flowduino ESPressio Development Platform.
33

4+
Light-weight and easy-to-use Threading for your Microcontroller development work.
45

6+
## Latest Stable Version
7+
There is currently no stable released version.
8+
9+
## ESPressio Development Platform
10+
The **ESPressio** Development Platform is a collection of discrete (sometimes intra-connected) Component Libraries developed with a particular development ethos in mind.
11+
12+
The key objectives of the ESPressio Development Platform are:
13+
- **Light-weight** - The Components should always strive to optimize memory consumption and operational overhead as much as possible, but not to the detriment of...
14+
- **Ease of Use** - Many of our components serve as Developer-Friendly Abstractions of existing procedural code libraries.
15+
- **Object-Oriented** - A `type` for everything, and everything in a `type`!
16+
- **SOLID**:
17+
- - > **S**ingle Responsibility Principle (SRP)
18+
Break your code into smaller, focused components.
19+
- - > **O**pen/Closed Principle (OCP)
20+
Be open for extension but closed for modification.
21+
- - > **L**iskov Substitution Principle (LSP)
22+
Be substitutable for the base type without altering correctness.
23+
- - > **I**nterface Segregation Principle (ISP)
24+
Break interfaces into specific, client-focused ones.
25+
- - > **D**ependency Inversion Principle (DIP)
26+
Be dependent on abstractions, not concretions.
27+
28+
To the maximum extent possible within the limitations/restrictons/constraints of the C++ langauge, the Arduino platform, and Microcontroller Programming itself, all Component Libraries of the **ESPressio** Development Platform must strive to honour the **SOLID** principles.
29+
30+
## License
31+
ESPressio (and its component libraries, including this one) are subject to the *Apache License 2.0*
32+
Please see the [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) accompanying this library for full details.
33+
34+
## Namespace
35+
Every type/variable/constant/etc. related to *ESPressio* Threads are located within the `Threads` submaspace of the `ESPressio` parent namespace.
36+
37+
The namespace provides the following (*click on any declaration to navigate to more info*):
38+
- [`ESPressio::Threads::IThread`](ithread)
39+
- [`ESPressio::Threads::Thread`](thread)
40+
- [`ESPressio::Threads::Manager`](threadmanager)
41+
- [`ESPressio::Threads::GarbageCollector`](garbagecollector)
42+
- [`ESPressio::Threads::IThreadSafe`](ithreadsafe)
43+
- [`ESPressio::Threads::Mutex`](mutex)
44+
- [`ESPressio::Threads::ReadWriteMutex`](readwritemutex)
45+
46+
## Platformio.ini
47+
You can quickly and easily add this library to your project in PlatformIO by simply including the following in your `platformio.ini` file:
48+
49+
```ini
50+
lib_deps =
51+
https://github.com/Flowduino/ESPressio-Threads.git
52+
```
53+
54+
Please note that this will use the very latest commits pushed into the repository, so volatility is possible.
55+
This will of course be resolved when the first release version is tagged and published.
56+
This section of the README will be updated concurrently with each release.
57+
58+
## Understanding Threads
59+
Threads enable us to perform concurrent and/or parallel processing on our microcontroller devices.
60+
In the case of multi-core microcontrollers, such as the ESP32, we can achieve true concurrent execution by using the components provided here in the *ESPressio* Thread Library.
61+
62+
By default, when an instance of a [`Thread`](thread) descendant is created, presuming that you do not modify by calling `SetCoreID()` prior to initializing the instance, the Thread [`Manager`](threadmanager) will automatically allocate the Thread to the next CPU Core.
63+
64+
For example, by default, your first Thread Instance will occupy *CPU 0*, your second will occupy *CPU 1*, your third will co-occupy *CPU 0*.
65+
66+
However, as hinted previously (and as you'll see later in this document) you can very easily define explicitly which CPU Core you want your `Thread` to run on.
67+
68+
Now, when your Microcontroller doesn't have multiple CPU Cores, or when you have multiple threads co-tenanting the same CPU Cores, Threads will operate on the princpals of *Time Slicing*. This is where `Thread`s are executed in *Parallel* (not the same as *Concurrent*), and they each get slices of time within which to continue execution.
69+
70+
In this way, multiple distinct contexts can be progressed without having to wait for each of them to complete in turn.
71+
72+
## Thread Safety
73+
Those of you familiar with multi-threading will already be aware of the need to enforce careful *thread-safety* when working with multiple `Thread`s.
74+
75+
*ESPressio* Threads makes it easy, providing multiple choices of *Thread-Safe Locks* for you to easily use.
76+
77+
You'll see an example later in this document.
78+
79+
## Basic Usage
80+
ESPressio Threads have been designed with ease of use in mind.
81+
82+
Ultimately, they are a carefully *Managed Encapsulation* of `Task`s, abstracted to operate and interface more alike a true `Thread` in modern desktop and mobile development.
83+
84+
Let's take a look at a really simple implementation:
85+
86+
### Includes...
87+
Before we define our `Thread`, we need to include the required header:
88+
```cpp
89+
#include <ESPressio_Thread.hpp>
90+
```
91+
92+
### Namespaces...
93+
Given that *ESPressio* Threads uses multi-tier Namespacing throughout, let's declare our Namespace so that we can reference the necessary type identifiers with less code:
94+
```cpp
95+
using namespace ESPressio::Threads;
96+
```
97+
98+
### A `Thread` type...
99+
With the required header linked, and the namespace defined, we can now define a simple `Thread` type, which we shall call `MyFirstThread`:
100+
```cpp
101+
class MyFirstThread : public Thread {
102+
protected:
103+
void OnInitialization() override {
104+
// Anything we need to do here prior to the Thread's Loop sstarting
105+
}
106+
107+
void OnLoop() override {
108+
// Whatever we want to do within the Loop
109+
}
110+
};
111+
```
112+
>NOTE: It is not necessary to override `OnInitialization` unless you have a reason. It is virtual, not abstract.
113+
114+
We shall be building from this basic example `class` throughout the rest of this documentation!
115+
116+
So, the above class declaration doesn't really do anything... let's build upon it to illustrate how multiple `Thread`s work:
117+
```cpp
118+
class MyFirstThread : public Thread {
119+
private:
120+
int _counter = 0;
121+
protected:
122+
void OnInitialization() override {
123+
// Anything we need to do here prior to the Thread's Loop sstarting
124+
}
125+
126+
void OnLoop() override {
127+
_counter++; // Increment the counter
128+
129+
// Let's display some information about our Thread...
130+
Serial.printf("MyFirstThread::OnLoop() - Thread #%d - On CPU %d, Counter = %d", GetThreadID(), xPortGetCoreID(), _counter);
131+
132+
delay(1000); // Let's let this Thread wait for 1 second before it loops around again
133+
}
134+
};
135+
```
136+
With the above changes, any instance of `MyFirstThread` will execute its `OnLoop()` method every one second, and each time it does, it'll increment a *counter*, then print out the following information in the `Serial` console:
137+
- The Thread ID
138+
- Which CPU the Thread is running on
139+
- The value of the *Counter*
140+
141+
Admittedly, this isn't the most practical use of a `Thread`, however, it is an *illustrative* one.
142+
143+
### The `setup()` method...
144+
Let's quickly assemble a program to use `MyFirstThread`:
145+
```cpp
146+
MyFirstThread thread1;
147+
148+
void setup() {
149+
Serial.begin(115200);
150+
151+
delay(500); // Small delay just so that the thread doesn't start before the Serial Monitor is ready
152+
153+
thread1.Initialize();
154+
}
155+
```
156+
That's all there is to it! If you push this program to your (compatible) microcontroller, it will immediately start printing the following into your Serial console (once per second):
157+
158+
>MyFirstThread::OnLoop() - Thread 1 - On CPU 0, Counter = 0
159+
MyFirstThread::OnLoop() - Thread 1 - On CPU 0, Counter = 1
160+
MyFirstThread::OnLoop() - Thread 1 - On CPU 0, Counter = 2
161+
MyFirstThread::OnLoop() - Thread 1 - On CPU 0, Counter = 3
162+
163+
### What about the `loop()` method?
164+
Your existing `loop()` method will continue to operate exactly as it always has. On the ESP32, the default `loop()` method executes on CPU 1, while you will notice that your instance of `MyFirstThread` (`thread1` in the above sample code) is running on CPU 0.
165+
166+
### The sample code so far...
167+
To make it easier to refer up and down, let's combine all of the code together now:
168+
```cpp
169+
#include <ESPressio_Thread.hpp>
170+
171+
using namespace ESPressio::Threads;
172+
173+
class MyFirstThread : public Thread {
174+
private:
175+
int _counter = 0;
176+
protected:
177+
void OnInitialization() override {
178+
// Anything we need to do here prior to the Thread's Loop sstarting
179+
}
180+
181+
void OnLoop() override {
182+
_counter++; // Increment the counter
183+
184+
// Let's display some information about our Thread...
185+
Serial.printf("MyFirstThread::OnLoop() - Thread #%d - On CPU %d, Counter = %d", GetThreadID(), xPortGetCoreID(), _counter);
186+
187+
delay(1000); // Let's let this Thread wait for 1 second before it loops around again
188+
}
189+
};
190+
191+
MyFirstThread thread1;
192+
193+
void setup() {
194+
Serial.begin(115200);
195+
196+
delay(500); // Small delay just so that the thread doesn't start before the Serial Monitor is ready
197+
198+
thread1.Initialize();
199+
}
200+
```
201+
202+
### Multiple Threads? No Problem!
203+
So we've created one separate thread (ideally to execute on a separate CPU Core from the default application thread)... but what if we want more threads?
204+
205+
That's really not a problem.
206+
207+
Let's modify the previous example to create multiple Threads:
208+
```cpp
209+
MyFirstThread thread1, thread2, thread3;
210+
211+
void setup() {
212+
Serial.begin(115200);
213+
214+
delay(500); // Small delay just so that the thread doesn't start before the Serial Monitor is ready
215+
216+
thread1.Initialize();
217+
thread2.Initialize();
218+
thread3.Initialize();
219+
}
220+
```
221+
222+
We've now added two additional threads, so our output will look something like this:
223+
>MyFirstThread::OnLoop() - Thread 1 - On CPU 0, Counter = 1
224+
MyFirstThread::OnLoop() - Thread 2 - On CPU 1, Counter = 1
225+
MyFirstThread::OnLoop() - Thread 3 - On CPU 0, Counter = 1
226+
>MyFirstThread::OnLoop() - Thread 1 - On CPU 0, Counter = 2
227+
MyFirstThread::OnLoop() - Thread 3 - On CPU 0, Counter = 2
228+
MyFirstThread::OnLoop() - Thread 2 - On CPU 1, Counter = 2
229+
230+
The explicit maximum number of *ESPresio* `Thread`s supported by the library is 256, however the *practical limit* depends entirely on the specifications of your microcontroller. It's almost certainly going to be considerably lower than 256!
231+
232+
### The Thread Manager
233+
In the previous example, you'll see that we manually called `Initialize()` on each instance of `MyFirstThread`.
234+
235+
Well, *ESPressio* Threads provides a central Thread `Manager`, and all of your `Thread` instances automatically register themselves with this `Manager`.
236+
237+
This neans we can `Initialize()` all of our `Thread` Instances in a single command!
238+
239+
First we need to make sure we include the `Thread` `Manager`'s header in our program:
240+
```cpp
241+
#include <ESPressio_ThreadManager.hpp>
242+
```
243+
Now we can modify the previous code example accordingly:
244+
```cpp
245+
MyFirstThread thread1, thread2, thread3;
246+
247+
void setup() {
248+
Serial.begin(115200);
249+
250+
delay(500); // Small delay just so that the thread doesn't start before the Serial Monitor is ready
251+
252+
Manager::Initialize();
253+
}
254+
```
255+
Now, all three of our `MyFirstThread` instances will start exactly as they did before, but we didn't have to explicitly `Initialize()` each of them separately.

0 commit comments

Comments
 (0)