The main advantage of protothreads over ordinary threads is that they are really lightweight: all protothreads run on the same stack, and the context switch coincides with the “rewind” of the stack.
It follows that defining a protothread has the same complexity as defining a function C, to which a “prologue” and an “epilogue” must be added. This is done with the MR_PT_THREAD
annotation, followed by the MR_PTI_BEGIN
and finalized by MR_PTI_END
annotation.
MR_PT_THREAD(function) { MR_PTI_BEGIN(); // ... logic ... MR_PTI_YIELD(); // ... logic ... MR_PTI_END(); }
The absence of the stack is an advantage in systems with strict memory constraints, that is, those in which a stack for a thread could use a large part of the available memory. However, with protothreads, this does not happen.
A protothread requires only two bytes of memory to function and this space is reserved by allocating at least one variable of the type mr_protothread
:
mr_protothread functionThread;
As you can see, the MR_PTI_YIELD
function is called at least once in the function. This call is necessary because, as we have anticipated, they are cooperative and not preventive threads: that is, there is the need for the programmer to indicate the best point where the execution can be blocked, and from which to resume.
To start the threads it is sufficient to call (continuously) the function with the following syntax:
while(1) { function(&functionThread); }
Note how it is possible to execute N
protothreads with the same code, allocating N
variables of type mr_protothread
and recalling, in sequence, each of them:
mr_protothread functionThread1; mr_protothread functionThread2; ... mr_protothread functionThreadN; ... while(1) { function(&functionThread1); function(&functionThread2); ... function(&functionThreadN); }
Move to COORDINATE THE PROTOTHREADS.