Earlier quoted context omitted.
> So it's equivalent to async, but it's still synchronous. Nice. Based on your description this is equivalent to async/await implemented with callbacks but not async/await implemented via polling.
Do you mean polling, as in calling poll()? Or polling, as in continuously checking if something is finished?
How to think about async/await in Rust
201–210 of 268 posts
Re: How to think about async/await in Rust
#202Earlier quoted context omitted.
> Your comment seems to be conflating concurrency with parallelism. No, it really doesn't. I mention both concurrency and parallelism, and their main difference. > Threads are the opposite: They are interfaces for parallel programming No, they are not. Threads can do both. When waiting for an i/o bound operation, a thread can simply sleep. Added bonus: A thread basd implementation supports io bound concurrency and cp…
> When waiting for an i/o bound operation, a thread can simply sleep. I mean if you're fine with blocking I/O then obviously you don't need async, but on the other hand having non-blocking I/O is the whole point of async ^^
Most node code I see in the wild is just a simple `await loadData()` which doesn't block the main node thread but does block that code flow until the data returns. This is roughly the same as what would happen in a normal blocking multithreaded language other than the extra overhead of a thread. If you don't have enough threads (or they are efficent enough in your language of choice) for this overhead to be an issue then you are adding all this complexity for almost no benefit.
Basically it comes down to if you trust your language of choice's threads more or less than your language of choice's event scheduler. Since Node is fully single threaded there isn't really an option but with other languages, a single thread per worker is much simpler.
In python it is even more opaque which to use as the CPython itself is singled threaded so you are comparing its thread implementation to its event scheduler implementation. For this small win you get to rewrite all your code to new, none-standard apis.
Re: How to think about async/await in Rust
#203Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…
What changed my mind was accidentally making a (shitty/incomplete) async system while implementing a program "The Right Way" using threads and synchronization primitives. The program is for controlling an amateur telescope with a lot of equipment that could change states at any moment with a complex set of responses to those changes depending on what exactly the program is trying to accomplish at the time. Oof, that was a confusing sentence. Let's try again; The telescope has equipment like a camera, mount, guide scope, and focuser that all periodically report back to the computer. The camera might say "here's an image" after an exposure is finished, the mount might say "now we're pointing at this celestial coordinate", the focuser might say "the air temperature is now X", and the guide scope might say "We've had an error in tracking". Those pieces of equipment might say those things in response to a command, or on a fixed period, or just because it feels like it.
Controlling a telescope can be described as a set of operations. Some operations are fairly small and well contained, like taking a single long exposure. Some operations are composed of other operations, like taking a sequence of long exposures. Some operations are more like watchdogs that monitor how things are going and issue corrections or modify current operations. When taking a sequence of long exposures the program would need to issue commands to the telescope depending on which of those messages it receives from the telescope or the user; If the tracking error is too high (or the user hits a "cancel" button) we might want to cancel the current exposure. If the air temperature has changed too much we might want to refocus after the currently running exposure is finished. If the telescope moves to a new celestial coordinate we probably want to cancel the exposure sequence entirely. So, how do we manage all that state?
The way I solved it was to make a set of channels to push state changes from the telescope or user. Each active operation would be split into multiple methods for each stage of that operation, and they would return an object that held the current progress and what it needed to wait on before we could move onto the next stage. That next stage would be triggered by a controlling central method that listened for all possible state changes (including user input) and dispatch to the next appropriate method for any of the operations currently running. To make things a little simpler I made a common interface for that object that let the controlling central method know what to wait on and what to call next. This allowed me the most control over how different concurrent operations were running while staying completely thread-safe. It was great, I could even listen to multiple channels at the same time when multiple operations were happening concurrently.
At this point I realized I'd accidentally made an async system. The central controlling method is the async runtime. The common interface is a Future (in rust, or Promise in js, or Task in C#). Splitting an operation into multiple methods that all return a Future is the "await" keyword. Once I accepted my async/await future, operations that were previously split across multiple methods with custom data structures to record all of the intermediate stages evaporated and became much more clear.
I'm still using multiple threads for the problems that benefit from parallel computation, but making use of the async system in rust has made implementing new operations much easier.
Re: How to think about async/await in Rust
#204Earlier quoted context omitted.
> The scenario you are describing is one were 64-128 OS threads are fully blocked waiting for IO. If that's the case, is it likely that you will have additional unused IO resources that could be being utilized? One likely scenario is that you've issued 128 RPCs to some other services and are waiting to hear back. Even if each RPC is, say, on a separate TCP connection, your network stack can handle plenty more. > Also…
Thanks for the reply. I am still having a hard time seeing why "turning up the number of threads" doesn't solve this. Maybe for languages with JIT runtimes where each process occupies a larger piece of memory, that could be a problem. But then I see virtual memory coming in, because as you say, most of those processes are doing nothing. I think I'm going to do some research and see what benchmarks/measurements I can…
Also can say at least that paging to/from disk or using memory compression would dominate all other overheads, and it's not something to rely on here.
Re: How to think about async/await in Rust
#205Earlier quoted context omitted.
Yeah it blocks the thread, any other "user work" needs to happen on a different thread. But if you just need multiple non-blocking IO operations run in parallel it's as simple as it gets. (the operating system's thread scheduler is basically the equivalent to the JS "event loop").
Right, so it's less efficient than async. Async would let you yield at the gather.
Taking into account that an async/await runtime also needs to switch to a different "context", the performance difference really shouldn't be all that big, especially if the task scheduler uses fibers (YMMV of course).
Re: How to think about async/await in Rust
#206Earlier quoted context omitted.
Yeah it blocks the thread, any other "user work" needs to happen on a different thread. But if you just need multiple non-blocking IO operations run in parallel it's as simple as it gets. (the operating system's thread scheduler is basically the equivalent to the JS "event loop").
Desktop operating systems all have application event loops that run within a single thread because the OS thread scheduler is not the same thing. If you just want an event loop, trying to use threads instead for everything will often end up in tears due to concurrent data access issues.
Re: How to think about async/await in Rust
#207Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…
That's just not true. It started because starting a thread for connection isn't scalable at all. Asynchronous programming was in use way before nodejs even in languages that have proper threads.
Re: How to think about async/await in Rust
#208Earlier quoted context omitted.
I reimplemented async, except that the event loop is only invoked explicitly and under the programmer's control. I know it doesn't sound like a lot, but it is.
Is the idea that you might be fanning out and want to delay starting the event loop? Fanning out in JS looks like: const fn = async (arg) => { ... }; // calls some RPC const results = await Promise.all(args.map(fn)) which might technically be starting the first func before the second is in the event loop, but I don't see why that matters for what I'm doing.
Re: How to think about async/await in Rust
#209Earlier quoted context omitted.
async event loops in Rust are invoked explicitly by the programmer as well.
Are they always invoked explicitly? Or is it sometimes implicit?
Re: How to think about async/await in Rust
#210Earlier quoted context omitted.
async event loops in Rust are invoked explicitly by the programmer as well.
Are they always invoked explicitly? Or is it sometimes implicit?