Samstag, 27. November 2010

protothread, a powerfull library

Some weeks ago, while browsing around the net i found Adam Dunkels website.
Back in 2005 he wrote protothread as an easy way to implement blocking conditions.
"Protothreads are a extremely lightweight, stackless threads that provides a blocking context on top of an event-driven system, without the overhead of per-thread stacks. The purpose of protothreads is to implement sequential flow of control without using complex state machines or full multi-threading. Protothreads provides conditional blocking inside a C function." [http://www.sics.se/~adam/pt/about.html]
Basically protothread is the implementation of stack less coroutines. Of course no real coroutines because the basic µc has only one core and no scheduler. What it does, is that it gives you the possibility to manage your code in a very easy way using 'thread a like functionality'. It consists of a few include files defining macro's that are used to implement threads as switch/case constructs.
The trick was originally invented by Tom Duff and dubbed Duff's device.

When you have a complex program consisting of many functions, maybe some options for user input and periodic calculations or sensor input to check for, you need to organize your code in a way that all functions are called 'often enough'. If not you risk to loose data or, in the case of user input, your user interface will lag. Often you'll have to check if a timer has run out and in case it has not, wait with further action until it has.

This is where protothreads come in very handy. They let you use the functions
  • void PT_WAIT_UNTIL(struct pt *pt, condition);
    Block and wait until condition is true.
  • void PT_WAIT_WHILE(struct pt *pt, condition);
    Block and wait while condition is true.
among some others. Look here for a condensed version of the protothreads API. But there are some constraints you need to consider:
protothreads do not save the stack context across a blocking call, local variables are not preserved when the protothread blocks. This means that local variables should be used with utmost care - if in doubt, do not use local variables inside a protothread!
So how do you use it?
Here's a protothread function skeleton:

#include <pt.h> 

static int protothread1(struct pt *pt) { 
    PT_BEGIN(pt); 
    while(1) {           
        PT_WAIT_UNTIL(pt, function_returns_true() ); 
        do_something(); 
    } 
    PT_END(pt);}

Here, the magic is happening: every time this function is called, the boolean condition of PT_WAIT_UNTIL() is evaluated again. So if this condition is a function that can return either true or false, the function_returns_true() is called and if the return value is not true, the protothread1() function stops here, until the next call. This means do_something() doesnt run this time.
Notice that protothread functions have to be declared static int.

And this is the way you have to initialize the protothreads and call them in your main loop
[Example from the pt documentation]

// every protothread needs an own struct pt variable
static struct pt pt1, pt2;
int main(void) {     
 /* Initialize the protothread state variables with PT_INIT(). */     
 PT_INIT(&pt1);     
 PT_INIT(&pt2);     
      
/* Then we schedule the two protothreads by repeatedly calling their     
 * protothread functions and passing a pointer to the protothread     
 * state variables as arguments.     */     
 while(1) {        
  protothread1(&pt1);        
  protothread2(&pt2);     
 }
}

"Many of us use the switch/case construct to explicitly implement concurrent state machines in our code. The [protothread] macros merely provide a level of abstraction above that so that the code appears more linear and the overall logic more visible."

So if this sounds interesting to you, why not download the protothread library and give it a shot?



UPDATE: i also wrote an example as a tutorial, see here

Keine Kommentare:

Kommentar veröffentlichen