How Protothreads Really Work (2024)

How protothreads really work

What goes on behind the magical macros in the C protothreads library?Why are they macros? How do the macros work? Read on for theexplanation.

In the C implementation of protothreads, all protothread operationsare hidden behind C macros. The reason for building the protothreadlibrary on C macros rather than C functions is that protothreads alterthe flow of control. This is typically difficult to do with Cfunctions since such an implementation typically would requirelow-level assembly code to work. By implementing protothreads withmacros, it is possible for them to alter the flow of control usingonly standard C constructs.

This page explains how protothreads work under the hood. We do this bytaking a look at how the C preprocessor expands the protothreadmacros, and by looking at how the resulting C code is executed.

First, we'll introduce a simple example program written withprotothreads. Since this is a simple program we can show the entireprogram, including the main() function from which the protothread isdriven. The code, shown below, waits for a counter to reach a certainthreshold, prints out a message, and resets the counter. This is donein a while() loop that runs forever. The counter is increased in themain() function.

#include "pt.h" static int counter;static struct pt example_pt; staticPT_THREAD(example(struct pt *pt)){ PT_BEGIN(pt); while(1) { PT_WAIT_UNTIL(pt, counter == 1000); printf("Threshold reached\n"); counter = 0; } PT_END(pt);} int main(void){ counter = 0; PT_INIT(&example_pt); while(1) { example(&example_pt); counter++; } return 0;}

Before we let the C preprocessor expand the above code, we'll take alook at how the protothread macros are defined. In order to makethings easier, we use a simpler definition than the actual definitionfrom the protothreads source code. (The definition used here is acombined version of the protothread macros and the local continuationmacros implemented with the C switch statement.) This definition looks like:

struct pt { unsigned short lc; };#define PT_THREAD(name_args) char name_args#define PT_BEGIN(pt) switch(pt->lc) { case 0:#define PT_WAIT_UNTIL(pt, c) pt->lc = __LINE__; case __LINE__: \ if(!(c)) return 0#define PT_END(pt) } pt->lc = 0; return 2#define PT_INIT(pt) pt->lc = 0

We see that the struct pt consists of a single unsigned shortcalled lc, short for local continuation. This unsignedshort variable is the source of the "two byte overhead" frequentlymentioned on the protothread web pages. Furthermore, we see that thePT_THREAD macro simply puts a char before its argument. Also,we note how the PT_BEGIN and PT_END macros open and close a Cswitch statement, respectively. But the PT_WAIT_UNTIL macro is themost complex looking of them all. It contains one assignment, onecase statement, one if statement, and even a returnstatement! Also, it uses the built-in __LINE__ macro twice. The__LINE__ macro is a special macro that the C preprocessor willexpand to the line number at which the macro is issued. Finally, thePT_INIT macro simply initializes the lc variable to zero.

Many of the statements used in the protothread macros are not commonlyused in macros. The return statement used in the PT_WAIT_UNTILmacro breaks the flow of control in the function the macro isused. For this reason, many people dislike the use returnstatements in macros. The PT_BEGIN macro opens a switchstatement, but does not close it. The PT_END macro closes acompound statement that it has not itself opened. These things doeslook weird when looked at without the perspective ofprotothreads. However, in the context of protothreads these things areabsolutely essential to the correct operation of protothreads: themacros has to change the flow of control in the C function inwhich they are used. This is indeed the whole point of protothreads.

Ok, enough of talk about how the protothread macros look weird toseasoned C developers. We now instead look at how the protothread inthe example above looks when expanded by the C preprocessor. To makeit easier to see what is happening, we put the original and theexpanded versions side by side.

staticPT_THREAD(example(struct pt *pt)){ PT_BEGIN(pt); while(1) { PT_WAIT_UNTIL(pt, counter == 1000); printf("Threshold reached\n"); counter = 0; } PT_END(pt);}
staticchar example(struct pt *pt){ switch(pt->lc) { case 0: while(1) { pt->lc = 12; case 12: if(!(counter == 1000)) return 0; printf("Threshold reached\n"); counter = 0; } } pt->lc = 0; return 2;}

At the first line of the code we see how the PT_THREAD macro hasexpanded so that the example() protothread has been turned into aregular C function that returns a char. The return value of theprotothread function can be used to determine if the protothread isblocked waiting for something to happen or if the protothread hasexited or ended.

The PT_BEGIN macro has expanded to a PT(switch) statement with anopen brace. If we look down to the end of the function we see that theexpansion of the PT_END macro contains the closing brace for theswitch. After the opening brace, we see how the PT_BEGIN expansioncontains a case 0: statement. This is to ensure that the codeafter the PT_BEGIN statement is the first to be executed the firsttime the protothread is run. Recall that PT_INIT set pt->lc to zero.

Moving past the while(1) line, we see that the PT_WAIT_UNTILmacro has been expanded into something that contains the number12. The pt->lc variable is set to 12, and a case 12:statement follows right after the assignment. After this, thecounter variable is checked to see if it has reached 1000 ornot. If not, the example() function now executed an explicitreturn! This all may seem surprising: where did the number 12 comefrom and why does the function return in the middle of thewhile(1) loop? To understand this we need to take a look at howthe code is executed in the example() function the next time it iscalled.

The next time the example() function is called from the main()function, the pt->lc variable will not be zero but 12, as itwas set in the expansion of the PT_WAIT_UNTIL macro. This makesthe switch(pt->lc) jump to the case 12: statement. Thisstatement is just before the if statement where countervariable is checked to see if it has reached 1000! So the countervariable is checked again. If it has not reached 1000, the example()function returns again. The next time the function is invoked, theswitch jumps to the case 12: again and reevaluates thecounter == 1000 statement. It will continue to do so until thecounter variable reaches 1000. Then, the printf statement isexecuted and the counter variable is set to zero, before thewhile(1) loop loops again.

But where did the number 12 come from? It is the line number of thePT_WAIT_UNTIL statement (check it by counting lines in theoriginal program on your screen!). The nice thing with line numbersare that they are monotonically increasing. That is, if we put anotherPT_WAIT_UNTIL statement later in our program, the line number willbe different from the first PT_WAIT_UNTIL statement. Therefore,the switch(pt->lc) knows exactly where to jump - there are noambiguities.

Did you notice anything slightly strange with the code above? Theswitch(pt->lc) statement jumped right into the while(1)loop. The case 12: statement was inside the while(1)loop! Does this really work? Yes, it does in fact work! This featureof the C programming language was probably first found by Tom Duff inhis wonderful programming trick dubbedDuff'sDevice. The same trick has also been used by Simon Tatham toimplementcoroutines in C (a terrific piece of code).

Ok, now that you know how protothreads work inside itswicked macros, download the library and try itout for yourself!

How Protothreads Really Work (2024)
Top Articles
Latest Posts
Article information

Author: Lakeisha Bayer VM

Last Updated:

Views: 6137

Rating: 4.9 / 5 (49 voted)

Reviews: 88% of readers found this page helpful

Author information

Name: Lakeisha Bayer VM

Birthday: 1997-10-17

Address: Suite 835 34136 Adrian Mountains, Floydton, UT 81036

Phone: +3571527672278

Job: Manufacturing Agent

Hobby: Skimboarding, Photography, Roller skating, Knife making, Paintball, Embroidery, Gunsmithing

Introduction: My name is Lakeisha Bayer VM, I am a brainy, kind, enchanting, healthy, lovely, clean, witty person who loves writing and wants to share my knowledge and understanding with you.