
ProtoThreads: Lightweight, Stackless Threads in C - retrocryptid
http://dunkels.com/adam/pt/
======
woodrowbarlow
the catch:

> Because 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!

~~~
banachtarski
That's literally what "stackless" implies though. I don't know if it's so much
of a catch as it is a difference between stackful and stackless routines.

~~~
thethirdone
> That's literally what "stackless" implies though

You could have threads with enough memory for the base levels local variables.
It would mean you wouldn't need global variables to keep state, but it would
still be stackless as you can't pause execution within a function call.

~~~
alksjdlaksj
>for the base levels local variables.

what does that even mean? Sure you could calculate what stacksize your exact
programm would need at maximum, if you unroll everything, but you could not
define a fixed "base level" of a stacksize for every single programm out
there, could you?

~~~
masklinn
> what does that even mean?

The topmost stack frame. That's what "stackless" generally implies: it doesn't
preserve the stack itself, that doesn't mean it preserves _nothing_.

That's what you see with most async/await systems (as opposed to more
integrated "lightweight thread" systems like Erlang or Go), yield points blow
the stack (though it may be relinked for debugging purposes), but they don't
break local variables.

------
ddeck
Discussion from a few months ago:

[https://news.ycombinator.com/item?id=19907229](https://news.ycombinator.com/item?id=19907229)

And 5 years ago:

[https://news.ycombinator.com/item?id=7854571](https://news.ycombinator.com/item?id=7854571)

------
gitgud
> _Protothreads can be used with or without an underlying operating system to
> provide blocking event-handlers. Protothreads provide sequential flow of
> control without complex state machines or full multi-threading._

So if it's still single-threaded, then this is basically a cumbersome _async
/await_ right?

~~~
fjfaase
Actually, it is a way to represent state machines as 'code'. It is a way of
seperating the data from the functions, such that the functions can be
'stopped' and 'continued' in the middle (turning them into coroutines). Each
stop point (often hidden in an await) is a state of the state machine
represented by the function.

------
butterisgood
All I know is the Contiki OS running on a Commodore 64 is(was?) quite capable
and used Protothreads a fair bit.

[http://contiki.sourceforge.net/docs/2.6/a01802.html](http://contiki.sourceforge.net/docs/2.6/a01802.html)

------
xiaodai
is this like an implementation of Go in the C language?

~~~
autumnal
No. Go uses _stackful_ coroutines. This is more like an implementation of
async/await in C.

~~~
Ericson2314
The catch is brutal. This looks like an implementation of goto in C.

~~~
banachtarski
How so? The decision between a stackful and a stackless coroutine is precisely
that, a decision. The tradeoff is that storing all registers/stack variables
and processor state before a yield and restoring it after a resume is a non-
trivial cost. For some applications, that cost may not matter, but for others,
it may be critical.

Also, this is less like a goto, and more like a setjmp/longjmp.

~~~
Ericson2314
A stack is an over approximation of the closure of the continuation.

You want something to create a union of all the relevant local variables of
each state. Doing this efficiently is non compositional and so requires
language support.

------
Quekid5
Nitpicks aside... if you're considering this: Why aren't you using a language
that better supports your use case?

(Don't get me wrong, this may be technically interesting, but... choose your
tools with care.)

~~~
learc83
Because if you're using this, you're likely using an embedded device with
limited memory and C or assembly are your only real options.

~~~
Quekid5
I realize it's been a while and you won't get any notification, but there
_are_ high-level languages that can compile to C code. Like e.g.
[https://ivorylang.org/](https://ivorylang.org/)

------
innagadadavida
So many poorly implemented hacks, why not just use libdispatch instead, you
get C blocks and it makes threading much more easy: dispatch_async(main_q, ^{
printf("Hello %s!\n", argv[i]); });

[https://apple.github.io/swift-corelibs-
libdispatch/tutorial/](https://apple.github.io/swift-corelibs-
libdispatch/tutorial/)

~~~
NobodyNada
From the first sentence of the article:

> Protothreads are extremely lightweight stackless threads designed for
> severely memory constrained systems, such as small embedded systems or
> wireless sensor network nodes. Protothreads provide linear code execution
> for event-driven systems implemented in C. Protothreads can be used with or
> without an underlying operating system to provide blocking event-handlers.
> Protothreads provide sequential flow of control without complex state
> machines or full multi-threading.

libdispatch has none of the features listed above. You're right that
libdispatch is a better choice for typical PC software -- but this is designed
for devices like an 8-bit microcontroller with a kilobyte of RAM.

~~~
innagadadavida
Any numbers on task creation/switching for protothreads? GCD tasks are
extremely lightweight: > Tasks in GCD are lightweight to create and queue;
Apple states that 15 instructions are required to queue up a work

Linear code execution can be done using a serial queue.

~~~
Const-me
GCD is just a convenience wrapper around thread pool. Probably inspired by
Windows thread pool API introduced in Vista few years before GCD, adding API
functions like SubmitThreadpoolWork.

Even on PCs with tons of resources, thread pools and coroutines are not always
interchangeable.

One reason is performance, accessing a cache line shared across cores is even
more expensive than cache miss.

Besides performance, writing same data from multiple threads, or doing in
parallel what must be running sequentially, is a common source of bugs.

------
kitd
As explained here [1], it uses a variation of Duff's device. So the claim that
_Protothreads were invented by Adam Dunkels with support from Oliver Schmidt_
is slightly misleading IMHO.

But fair play for turning it into a working library.

[1]
[http://dunkels.com/adam/pt/expansion.html](http://dunkels.com/adam/pt/expansion.html)

~~~
huhtenberg
Don't be mean like that.

Duff's device to ProtoThreads is what a paintbrush is to a painting.

One is a construct for conditional loop unrolling, but it takes a stroke of
genius to apply it in an absolutely orthogonal way to implement a functional
cooperative multithreading with just a handful of macros. Duff's device was
nowhere close to be meant for that, so your condescending pat on a back for
"turning it into a working library" is completely inappropriate.

~~~
mrpippy
ProtoThreads can use Duff's device to implement its "local continuations", but
it's not the only implementation.

There's an alternate one that uses the "labels as values" feature supported by
GCC and Clang, which is much more elegant. In particular, it allows you to
have a 'switch' statement inside the protothread.

~~~
gpderetta
I've tried that, but unfortunately, at least on GCC, taking the address of a
label suppresses inlining of the containing function (which is important of
you are trying to implement generators, less critical for async functions), so
it often generate worst code than the switch statement implementation.

