Iâm a contributor to Crossbeam, Tokio, and Rayon and am currently researching how to make all those concurrent programming models work together really well. This is a very interesting topic and will only become increasingly important in the near future. We should indeed explore it into more depth.
However, I still believe the reasoning behind the original post in this thread is flawed. While it would be amazing to have a unified concurrency model that abstract Crossbeam/Tokio/Rayon away as a purely low-level detail for parallel/asynchronous tasks, I think itâs a pipe dream and will never happen.
More specifically, this is what I disagree with: âRayon-like interfaces are a primitive in the language in much the same way as an allocatorâ. I wish that was true, but strongly doubt it. Those Rayon-like interfaces are alike only in the sense that they vaguely resemble each other, but are fundamentally different at their core:
-
Crossbeam works with native OS threads that may be non-cooperatively preempted at any time. Blocking is okay as long as we use thread::park(), Condvars, or similar primitives.
-
Rayon works with tasks that are cooperatively preempted in rayon::join() and at the end of rayon::scope(). Note that tasks technically have a stack, but preemption puts one tasksâs stack on top of another. Blocking in tasks is forbidden.
-
Tokio (Futures) works with tasks that are cooperatively preempted in await. They are truly stackless and in order to preempt a task we have to fully âundoâ its whole stack and return Poll::Pending from it. Blocking is forbidden (although tokio::blocking() can be used when blocking is absolutely necessary), but await can be used to simulate it.
There is one more concurrency model:
- Stackful coroutines like in the
may crate. They work with userspace threads that are cooperatively preempted. Blocking is forbidden unless custom APIs are used. Each spawned task gets its full dedicated stack and itâs possible to preempt at any time.
Those models cannot be unified because they all have completely incompatible rules around preemption, stacks, and blocking.
However, not all is lost. Instead of trying to find a common interface for them, I think we should pursue a slightly different goal: try to find primitives that will make them all work together.
For example, how does one spawn a CPU-intensive task from Tokio onto Rayon and await it? We donât have good answers for basic questions like that but we should! In my opinion, this is the real problem we should be solving, not building a common facade.