7 ms·
Hoare Was Right. (But if you're only firing up a few tasks, why not just use threads? To get a nice wrapper around an I/O event loop?)
by mrkline 3y ago
Hoare Was Right.
(But if you're only firing up a few tasks, why not just use threads? To get a nice wrapper around an I/O event loop?)
- tel 3y agoWaiting asynchronously on multiple channels/signals. Heterogenous select is really nice.
- mrkline 3y agoIt's great! But there's nothing about it that requires futures. It really annoys me that something like this isn't built-in: https://github.com/mrkline/channel-drain https://github.com/mrkline/channel-drain
- tel 3y agoThat works for channels, but being able to wait other asynchronous things is better. Timeouts for instance. We could imagine extending this to arbitrary poll-able things. And now we have futures, kind of.
- simias 3y agoIt really is, but I still favour "unsexy" manual poll/select code with a lot of if/elseing if it means not having to deal with async. I fully acknowledge that I'm an "old school" system dev who's coming from the C world and not the JS world, so I probably have a certain bias because of that, but I genuinely can't understand how anybody could look at the mess that's Rust's async and think that it was a good design for a language that already had the reputation of being very complicated to write. I tried to get it, I really did, but my god what a massive mess that is. And it contaminates everything it touches, too. I really love Rust and I do most of my coding in it these days, but every time I encounter async-heavy Rust code my jaw clenches and my vision blurs. At least my clunky select "runtime" code can be safely contained in a couple functions while the rest of the code remains blissfully unaware of the magic going on under the hood. Dear people coming from the JS world: give system threads and channels a try. I swear that a lot of the time it's vastly simpler and more elegant. There are very, very few practical problems where async is clearly superior (although plenty where it's arguably superior).
- lelanthran 3y ago> It really is, but I still favour "unsexy" manual poll/select code with a lot of if/elseing if it means not having to deal with async. > I fully acknowledge that I'm an "old school" system dev who's coming from the C world and not the JS world, so I probably have a certain bias because of that, but I genuinely can't understand how anybody could look at the mess that's Rust's async and think that it was a good design for a language that already had the reputation of being very complicated to write. I'm in the same "old school" system dev category as you, and I think that modern languages have gone off the deep end, and I complained about async specifically in a recent comment on HN: https://news.ycombinator.com/item?id=37342711 https://news.ycombinator.com/item?id=37342711 > At least my clunky select "runtime" code can be safely contained in a couple functions while the rest of the code remains blissfully unaware of the magic going on under the hood. And we could have had that for async as well, if languages were designed by the in-the-trenches industry developer, and not the "I think Haskell and Ocaml is great readability" academic crowd. With async in particular, the most common implementation is to color the functions by qualifying the specific function as async, which IMO is exactly the wrong way to do it. The correct way would be for the caller to mark a specific call as async. IOW, which of the following is clearer to the reader at the point where `foo` is called? Option 1: color the function async function foo () { // ... } ... let promise = foo (); let bar = await promise; Option 2: schedule any function function foo () { // ... } let sched_id = schedule foo (); ... let bar = await sched_id; Option 1 results in compilation errors for code in the call-stack that isn't async, results in needing two different functions (a wrapper for sync execution), and means that async only works for that specific function. Option 2 is more like how humans think - schedule this for later execution, when I'm done with my current job I'll wait for you if you haven't finished.
- jayd16 3y agoIsn't mixing async and sync code like this a recipe for deadlocks? What if your example code is holding onto a thread that foo() is waiting to use? Said another way, explain how you solved the problems of just synchronously waiting for async. If that just worked then we wouldn't need to proliferate the async/await through the stack.
- kccqzy 3y agoExactly. People are too afraid of using threads these days for some perceived cargo-cult scalability reasons. My rule of thumb is just to use threads if the total number of threads per process won't exceed 1000. (This is assuming you are already switching to communicating using channels or similar abstraction.)
- winternewt 3y agoI can't both perform blocking I/O and wait for a cancellation signal from another thread. So I need to use poll(), and async is a nice interface to that.
- dralley 3y ago99% of the use cases that ought to use async are server-side web services. If you're not writing one of those, you almost certainly don't need async.
- colejohnson66 3y agoOr desktop programs. Many GUI frameworks have a main thread that updates the layout (among other things) and various background ones.
- bluGill 3y agoAsync and GUI threads are different concepts. Of course most GUIs have an event loop which can be used as a form of async, but with async you do your calculations in the main thread, while with GUIs you typically spin your calculations off to a different thread. Most often when doing async you have a small number of tasks repeated many times, then you spin up one thread per CPU, and "randomly" assign each task as it comes in to a thread. When doing GUI style programming you have a lot of different tasks and each task is done in exactly one thread.
- jayd16 3y agoHmm I would say the concepts are intertwined. Lots of GUI frameworks use async/await and the GUI thread is just another concurrency pattern that adds lock free thread exclusivity to async tasks that are pinned to a single thread.
- adrienthebo 3y agoA particularly interesting use case for async Rust without threads is cooperative scheduling on microcontrollers[1]; this article also does a really good job of explaining some of the complications referenced in TFA. [1]: https://news.ycombinator.com/item?id=36790238 https://news.ycombinator.com/item?id=36790238
- nextaccountic 3y ago> (But if you're only firing up a few tasks, why not just use threads? To get a nice wrapper around an I/O event loop?) To get easier timers, to make cancellation at all possible (how to cancel a sync I/O operation?), and to write composable code. There are patterns that become simpler in async code and much more complicated in sync code.
- kprotty 3y agoYou cancel a sync IO op similar to how you cancel an async one: have another task (i.e OS thread in this case) issue the cancellation. Select semantically spawns a task per case/variant and does something similar under the hood if cancellation is implemented.
- nextaccountic 3y agoYou can do that, but then the logic of your cancellable thread gets intermingled with the cancellation logic. And since the cancellation logic runs on the cancellable thread, you can't really cancel a blocking operation. What you can do is to let it run to completion, check that it was canceled, and discard the value.
- kprotty 3y agoNot sure I follow; the cancellation logic is on both threads/tasks 1) the operation itself waiting for either the result or a cancel notification and 2) the cancellation thread sending that notification. The cancellation thread is generally the one doing the `select` so it spawns the operation thread(s) and waits for (one of) their results (i.e. through a channel/event). The others which lose the race are sent the cancellation signal and optionally joined if they need to be (i.e. they use intrusive memory).
- JonChesterfield 3y agoHe didn't say queues though. CSP isn't processes streaming data to each other through buffered channels, it's one process synchronously passing one message to another. Whichever one gets the the communication point waits for the other.
- dfawcus 3y agoIt is both. Hoare's later paper introduced buffered channels to CSP. So one can use it as synchronous passing, or queued passing.