5 ms·
> "I don’t want fast threads, I want futures" I don't really understand this point. In a hypothetical world where (a) threads are cheap, and (b) threads sup
by comex 3y ago
> "I don’t want fast threads, I want futures"
I don't really understand this point.
In a hypothetical world where
(a) threads are cheap, and
(b) threads support some form of cancellation (with an ecosystem that expects this and is built around it),
then you don't need "intra-task concurrency" as a fundamental primitive. You can achieve the exact same semantics by just having a helper function that, given N closures representing work to perform, spawns a new thread to run each one, and waits for any/all of them to finish, while making sure to cancel all those threads if it itself is cancelled. (Or if you want to manually manage future objects for more control, you can do that too. But a future just represents work being done on a thread.)
Compare to Go. Go has cheap threads. Boats criticizes Go's "threads, locks and channels", but the fact that you have to interact with those primitives directly, rather than using a helper function as described, is really a limitation of Go's type system, not an inherent result of basing concurrency on cheap threads.
With one exception: Go threads do not support cancellation. Cancellation is extremely important! Without it, you have to manually pass down timeouts and other forms of cancellation into each function that performs I/O, and it becomes impossible to write that kind of generic helper function.
And of course, Rust has to fit into an existing thread ecosystem that does not meaningfully support cancellation, and does not make threads cheap.
So the tradeoff for Rust remains. Still, I don't see this anywhere near the way boats sees it. In my ideal world, if I didn't care about performance or compatibility, I wouldn't want "a language in which all functions are coroutines, meaning that all functions can yield." I don't think yielding needs to be visible to the user in any meaningful way; why does it need to be? It can be a hidden implementation detail, just like it is with green threads or OS threads. But I'd add on a concept of cancellation.
…In short, I think my ideal model is something like Trio, but I haven't had the opportunity to actually use Trio. I hope that changes.
(edit: and I'm looking forward to when boats does respond to the Trio blog post.)
- nyanpasu64 3y agoDoes synchronous Rust (with no runtime and no tokio) have a cross-platform way for a parent thread (eg. a GUI) to wait for messages either from user input or a child thread (eg. audio device changed), and/or a child thread to wait for messages either from IO (eg. audio buffers ready to fill) or other sources (eg. audio timeout or cancellation as you mentioned)? In C++ on Windows you'd use WaitForMultipleObjects, on Unix you'd use poll on hardware/event file descriptors, Crossbeam channels have a select macro but I'm not sure if it's as real-time as WaitForMultipleObjects (officially recommended for WASAPI) or poll (officially recommended for ALSA), and it probably doesn't work outside of channels?
- Rusky 3y agoSynchronous Rust can just call those APIs like C++ can.
- nsm 3y agoTake a look at ConcurrentML and its implementations in Racket and Guile. Schemes have stackful coroutines (as a side effect of supporting delimited continuations), and CML builds green thread like semantics on top of that with a small set of elegant primitives, including cancellation. As an aside, when folks mention algebraic effects in statically typed languages, delimited continuations and effects are equally powerful (possibly identical?). IMO effects seem to be about type safety and static typing, which does allow some neat optimizations.
- dxdm 3y agoWhich trio blog post? I can't seem to find an "official" blog by the trio maintainers, but from what you wrote I'm interested in reading one.
- withoutboats3 3y agoThis is interesting and makes sense. It's worth mentioning that the original use of structured concurrency (by Sustrik) was really about dealing with the problem of cancellation-friendly virtual threads: https://250bpm.com/blog:71/ https://250bpm.com/blog:71/ > I don't think yielding needs to be visible to the user in any meaningful way; why does it need to be? It's an interesting question. Users definitely want coroutines they can yield themselves - this is was iteration is all about, for example, and there are other similar patterns (the entry API is ultimately a kind of coroutine). At the same time, the highest performance low level interfaces for asynchronous IO are ultimately just asynchronous iterators & asynchronous sinks over shared memory ring buffers, io-uring is the most famous example but there are others like AF_XDP. I think you're right that a kind of cancellable virtual thread ultimately would have analogous affordances to a future. But a future is just a specific class of coroutine (that yields a unit type), so if you will have the coroutine mechanism for the rest of the system, why would you not extend it to include IO instead of adding a separate mechanism to erase the fact that IO is happening? What you also get from this is that if a function yields the bottom type you know it doesn't perform IO.