OOSMOS is the Object-Oriented State Machine Operating System, which features support for generative hierarchical state machines, publish/subscribe event processing, and first-come first-served scheduling. OOSMOS leverages ProtoThreads to implement two scoped types of threading: state threads and object threads. See OOSMOS for C.
This C++ implementation is a specialized lite version of OOSMOS that supports static threads and object threads - concurrency for C++. MIT licensed.
(Assumes that Python is installed.)
On Windows, open a command line window whose environment variables are set up for any version of Visual Studio C++, VS 2013 through 2019. On Linux, simply open any terminal window.
Then run:
python build.py
This will build three example applications: static_threads
, object_threads
, and test_threads
.
This is an example of a thread at file scope.
#include "oosmos.hpp"
#include <iostream>
using namespace std;
static void HeartbeatThread(OOSMOS::cStack& rStack)
{
ThreadBegin();
for (;;) {
cout << "HeartbeatThread: Heartbeat On" << endl;
ThreadDelayMS(50);
cout << "HeartbeatThread: Heartbeat Off" << endl;
ThreadDelaySeconds(2);
}
ThreadEnd();
}
int main()
{
OOSMOS::cStack HeartbeatThread_Stack;
for (;;) {
HeartbeatThread(HeartbeatThread_Stack);
OS::DelayMS(1);
}
}
HeartbeatThread: Heartbeat On
HeartbeatThread: Heartbeat Off
HeartbeatThread: Heartbeat On
HeartbeatThread: Heartbeat Off
...
Here is an example of two object threads, where the thread functions are inside the scope of an object and that can access all the members of the containing object:
#include "oosmos.hpp"
#include <iostream>
using namespace std;
struct cMyObject : public OOSMOS::cObject
{
OOSMOS::cStack BlinkingThread_Stack;
void BlinkingThread(OOSMOS::cStack& rStack)
{
ThreadBegin();
for (;;) {
cout << "BlinkingThread: LED On" << endl;
ThreadDelayMS(250);
cout << "BlinkingThread: LED Off" << endl;
ThreadDelayMS(750);
}
ThreadEnd();
}
OOSMOS::cStack BeepingThread_Stack;
uint32_t m_BeepCount = 0;
void BeepingThread(OOSMOS::cStack& rStack)
{
ThreadBegin();
for (;;) {
m_BeepCount += 1;
cout << "BeepingThread: Beep " << m_BeepCount << endl;
ThreadDelaySeconds(2);
}
ThreadEnd();
}
void Run()
{
BlinkingThread(BlinkingThread_Stack);
BeepingThread(BeepingThread_Stack);
}
};
int main()
{
cMyObject MyObject;
for (;;) {
OOSMOS::Run();
OS::DelayMS(1);
}
}
BlinkingThread: LED On
BeepingThread: Beep 1
BlinkingThread: LED Off
BlinkingThread: LED On
BlinkingThread: LED Off
BeepingThread: Beep 2
BlinkingThread: LED On
BlinkingThread: LED Off
BlinkingThread: LED On
BlinkingThread: LED Off
BeepingThread: Beep 3
BlinkingThread: LED On
BlinkingThread: LED Off
BlinkingThread: LED On
BlinkingThread: LED Off
...
This example tests all the OOSMOS thread functions.
#include "oosmos.hpp"
#include <iostream>
using namespace std;
struct cMyObject : public OOSMOS::cObject
{
uint32_t m_ObjectMember = 0;
struct cTestThreadStack : public OOSMOS::cStack
{
int i;
bool TimedOut;
} TestThread_Stack;
void TestThread(cTestThreadStack& rStack)
{
ThreadBegin();
for (rStack.i = 1; rStack.i <= 5; rStack.i++) {
cout << "TestThread: Iteration " << rStack.i << endl;
ThreadDelayUS(300);
}
cout << "TestThread: DelaySeconds" << endl;
ThreadDelaySeconds(1);
cout << "TestThread: Yield" << endl;
ThreadYield();
m_ObjectMember += 1;
cout << "TestThread: WaitCond" << endl;
ThreadWaitCond(true);
cout << "TestThread: WaitCond_Timeout 1" << endl;
ThreadWaitCond_TimeoutMS(true, 100, &rStack.TimedOut);
AssertWarn(!rStack.TimedOut, "Should not have timed out.");
cout << "TestThread: WaitCond_Timeout 2" << endl;
ThreadWaitCond_TimeoutMS(false, 100, &rStack.TimedOut);
AssertWarn(rStack.TimedOut, "Should have timed out.");
cout << "TestThread: Exit (to ThreadFinally)" << endl;
ThreadExit();
cout << "TestThread: Should not get here" << endl;
ThreadFinally();
cout << "TestThread: Exiting" << endl;
ThreadEnd();
}
void Run()
{
TestThread(TestThread_Stack);
}
};
int main()
{
cMyObject MyObject;
for (;;) {
OOSMOS::Run();
OS::DelayMS(1);
}
}
TestThread: Iteration 1
TestThread: Iteration 2
TestThread: Iteration 3
TestThread: Iteration 4
TestThread: Iteration 5
TestThread: DelaySeconds
TestThread: Yield
TestThread: WaitCond
TestThread: WaitCond_Timeout 1
TestThread: WaitCond_Timeout 2
TestThread: Exit (to ThreadFinally)
TestThread: Exiting
...
The program does not terminate. You must press CNTL-C
to exit.
One of the severe limitations of the original C implementation of ProtoThreads is that any values stored in variables on the runtime stack are not preserved from one invocation of the thread function to the next. This implementation addresses this limitation by introducting a lightweight stack per thread where the programmer can store values from one invocation to the next in a clean, reliable, and readable way. Call it a ProtoStack. In the example above, see how the TestThread
function uses the members of the cTestThreadStack
class.
For a detailed walk-through of how ProtoThreads work, visit HOW-PROTOTHREADS-WORK.md.
- You must call each thread function periodically.
- If you use object threads, you must override virtual function
Run()
in each object that you create and then call each thread function in the object, in turn. - On Windows and Linux, you'll want to throttle the calls to thread functions with a hard delay in order to be polite to others on the system. See
OS::DelayMS(1)
in the example. Vary the delay time depending on how responsive you need your application to be. - On 'bare metal' (Arduino, PIC32, STM32, etc.), you'll want to run without throttling.
- If you use object threads, you must override virtual function
- You must allocate a stack for each thread.
- If you have iterators or need other variables local to the function, you must specialize
OOSMOS::cStack
and allocate them there (seecTestThreadStack
in theTestThreads
example). - If you don't need local variables, then simply allocate a stack of type
cStack
. SeeBeepingThread_Stack
in the example.
- If you have iterators or need other variables local to the function, you must specialize
- You must pass at least one argument to each thread function that is a reference to the thread's stack that you created. The name of the argument must be
rStack
. - For new platforms, implement a new
os_<name>.cpp
file that conforms to the modest interface specified inos.hpp
.
A simple hungarian notation is used.
m_
- Member variable.c
- Class.r
- Reference.
This is MIT licensed. Feel free to change to suit your preferred style.