
Green threads explained in 200 lines of Rust - taheris
https://cfsamson.gitbook.io/green-threads-explained-in-200-lines-of-rust/
======
FullyFunctional
Co-routines are very useful and likely underused, but sometimes you are
actually better off being able to pass the control to a given thread directly,
other than having a scheduler involved.

Anecdote: almost a decade ago, I was responsible for an NVMe-like
implementation (hard- and software). The 3rd version of the firmware
recognized the various components as threads, but there was no need for
preemption (which would require expensive locking). Traditional scheduling
would work, but you actually know exactly which thread should execute next
(hardware will signal done), so an explicit yield_to() was far cheaper and
only slightly more expensive than a function call.

~~~
PopeDotNinja
If you have any knowledge of it, what are your thoughts about the Erlang
scheduler & Erlang's concurrency model?

~~~
FullyFunctional
Funny enough we actually started prototyping with Erlang for subsequent
project (which was cancelled before it went far). Unfortunately I don't know
enough to know what's special about the Erlang scheduler (if anything), but as
I understand the Erlang concurrency model, it's mostly about not sharing
memory (forces explicit communication). That's obviously going to eliminate a
host of bugs, but it would have been way too expensive for the mentioned
firmware.

Once we got started with Erlang I was pretty turned off. The pretty examples
you see in tutorials aren't what you'll be using. Instead it's framework upon
framework, far from elegance IMO. I was happy to not have to deal with that
again. Today I'd probably choose Rust for the same task (static types FTW).

(EDIT: typos)

~~~
PopeDotNinja
Given that Erlang is sufficiently different from anything else I've seen, it
doesn't surprise me that trying to be productive in Erlang before you knew it
well enough was suboptimal experience. I like it, and more specifically
Elixir, quite a bit, but the learning curve was steep.

~~~
verttii
It's fair to say Erlang's strength or appeal is not the language itself but
the platform you instruct with it. That's also where the learning curve is.

------
identity0
I feel like "200 lines of Rust" is a little misleading considering how much
explaination there is. It's like saying you can learn all of string theory in
0 lines of Python.

~~~
mlevental
it seems like in every post there's a naysayer about something. you're
complaining about an article that explains a difficult topic extensively and
generously - in my day you had to pay really really good money for that. who
cares if the title focuses one aspect of the explanation? sure it's a little
deceptive but it's such a minor cost to you that you literally spent more time
complaining than they deceived you out (click link with false notion, see
length, close page). LPT: being critical in and of itself does not make you
look smart.

------
amluto
Part of the code is:

    
    
        unsafe {
            std::ptr::write(stack_ptr.offset(SSIZE - 16) as *mut u64, hello as u64);
            ctx.rsp = stack_ptr.offset(SSIZE - 16) as u64;
            gt_switch(&mut ctx, &mut n);
        }
    

Only the last line should be unsafe — the first line appears to be writing in
bounds to a Vec, which is easy (and much more readable) in safe Rust.

Linux used to do its actual context switch in C with inline asm, and it
changed to being in straight asm a while back. This was absolutely a win.
Rather than trying to make everything work out behind the compiler’s back,
it’s much more straightforward to write a function in asm that does the stack
switch.

~~~
oconnor663
The ptr::write is needed because the stack vector contains bytes and the code
wants to do a pointer cast and write a u64. It could be done in safe Rust with
u64::to_ne_bytes and a copy_from_slice or something like that, but since all
of this code is extravagantly unsafe anyway, I think it's reasonably clear to
Just Do It :)

------
amelius
Isn't one downside that the inline assembly will not be peephole-optimized by
the code generator (LLVM)? E.g., you'd be saving and restoring registers that
are not even used.

~~~
chc4
Not really.

You potentially don't know who is "resuming", and so don't know what registers
they will clobber. It would only be a "downside" if 1) your code uses a
register 2) no other possible green threads do, and that isn't an invariant
any compiler I know will promise, especially in the face of FFI calls.

If you're at the point where you want to do register allocation and spilling
optimization across multitasking points, you're probably better off writing
your own compiler instead of expecting a thread runtime to do it for you.

For comparison, _normal_ threads have the same downside: the kernel saves and
restores all registers (even more than what the example does), so you're not
doing any worse than that.

------
swolchok
FWIW, there is POSIX-specified functionality (swapcontext and friends) for
changing the user thread context:
[http://pubs.opengroup.org/onlinepubs/009695399/functions/swa...](http://pubs.opengroup.org/onlinepubs/009695399/functions/swapcontext.html)

~~~
asdfasgasdgasdg
It's very slow though, at least if you believe the Boost docs. They have
various context-controlling types. One of them is called ucontext_t, and falls
back to ucontext. Then there's fcontext_t, which is basically a more advanced
version of this article. Boost's docs claim it is much faster than ucontext.

------
mhh__
[https://github.com/dlang/druntime/blob/v2.086.0/src/core/thr...](https://github.com/dlang/druntime/blob/v2.086.0/src/core/thread.d#L4071)

D's implementation ^ (Linked partly because the inline assembly is very clear)

------
kccqzy
Great explanation! Although I have to wonder, why did the author choose to use
Rust for this project? As far as I can see, it didn't really use any of Rust's
advanced features not available in C or C++, like tagged unions, pattern
matching, advanced type system, traits, borrow checking…

For what it's worth, I translated the whole code into C++. And it's
essentially the same:
[https://gist.github.com/kccqzy/c404b8614f39f854f137dcc9284b0...](https://gist.github.com/kccqzy/c404b8614f39f854f137dcc9284b0b87)
And it runs correctly on macOS and Linux when compiled with clang.

~~~
kasane
Why would one pick C or C++ over Rust for this project?

~~~
sim_card_map
Much easier to build, for me at least.

I tried installing Rust and building the example, but then it complained about
nightly features, and I didn't have time to figure that out.

C/C++ are mainstream languages, and building is as simple as g++ test.cpp.

~~~
eridius
Using nightly features is as simple as inserting +nightly as the first
argument to rustc or cargo (assuming rust was installed with rustup, which is
the default way to install it).

So in this case, it's just `rustc +nightly green_threads.rs`

------
bluejekyll
The explanation of the asm! macro is great, I’ve always been a little confused
by it.

It does make me thinking about how this could be useful in the context of
Futures and async/await.

~~~
steveklabnik
> It does make me thinking about how this could be useful in the context of
> Futures and async/await.

At its core, when you pass a Future (really, a chain of futures, but in the
end that's still a Future) to an executor, the executor creates a stack for
it. An executor with multiple futures will then switch between them, sorta
similar to the code seen here. The details depend on the exact executor, of
course, but the fundamental idea is the same.

One of the nice things about the futures design is that you can know exactly
how big the stack needs to be, so the resizing stuff isn't an issue.

~~~
cfsamson
The long term plan for this was/is to actually connect them to futures and the
async story in rust, adapting this to be an executor and implementing our own
futures (though I probably have to implement a simple reactor and fake some IO
operation in e seperate thread as well). However, it's quite a bit of rework
and better to seperate this in two parts, but I feel it could be a good way to
build brick by brick towards a pretty good understanding for those interested.

------
joaomoreno
Beautiful docs

------
iamcreasy
Now that there is a lot more interest in green threads, I was wondering if
anybody knows why back in the days Java dropped the support of green thread in
favor of native thread?

------
fc_barnes
Daily reminder that goroutines don't require a context switch.

~~~
steveklabnik
A _kernel_ context switch. Neither do the ones described here.

