4 ms·
The only reason you wanted to use “async” was because of micro-optimization on thread context switches?
by lilyball 6y ago
The only reason you wanted to use “async” was because of micro-optimization on thread context switches?
- IshKebab 6y agoThat's the most commonly given reason for using async/await so a lot of people assume threads are way more heavyweight than they actually are. What other reason were you thinking of?
- AaronFriel 6y agoEase of understanding multithreaded code and wait on results or perform standard control flow constructs in a multithreaded environment? This is a great example in Node on useful combinators that with async await make it easy to express parallel programming concepts with familiar tools. No manual IPC, no fork/join child PID/thread ID handling, etc. https://github.com/sindresorhus/promise-fun https://github.com/sindresorhus/promise-fun The same abstractions (or many of them) exist in Rust, but I think the above is illustrative of the ways we can combine async object returning functions and then use await to hide the complexity of the state machines needed to drive them. That this abstraction that makes code easy to read and write also performs better is the icing on the cake. The former prevents bugs and keeps code quality high, and that is worth much more.
- IshKebab 6y ago> Ease of understanding multithreaded code Rust is not Javascript. Using threads is actually a lot simpler in Rust than async/await.
- IgorPartola 6y agoI don’t know about Rust but in every other language I’ve used threads were easy to use and understand, except when it came to some bits like signals, which at least on Linux are no longer a big problem. Main thread runs a hot loop to look for data to process, then hands it off on a queue to a worker thread out of a pool. That thread is then solely responsible for processing the event and passing the result either back to the main thread or to the next thread in the pipeline via the same queue mechanism. Last thread to handle the result or the exception frees the resources. It might not be ergonomic for all types of code but it certainly isn’t hard to understand what everything is doing and easy enough to debug since each thread can be tested individually to check its functionality.
- IshKebab 6y agoSure, threads are easy to understand. The difficult is when you get a concurrency bug but that can happen with single threaded async/await code anyway. Also threads are definitely not easy to use in all languages. E.g. C++ gives you very little help (no channels for example), and JavaScript makes starting threads difficult and moving/sharing memory is limited to primitive arrays.
- IgorPartola 6y agoA queue implementation in C is easy to create and understand if you don’t have a library for it handy. Combined with a mutex and/or a spin lock and once you’ve grokked pthreads’ mental model you should have the primitives. But those are all guns that shoot both ways if you aren’t careful.
- AaronFriel 6y agoYou just described having the main thread have to wait on multiple threads to complete processing of data, worker threads handling signals and IPC and moving data between threads, and then some sort of shared signaling to ensure resources are freed. So, code potentially laden with use after frees, double frees, shared and mutable data, and so on. No offense to you, but I would be leery of trusting that code in any languages except a handful. Certainly not C/C++, and if it were written in Rust, I would hope it would use a thread combinator library and channels.
- IgorPartola 6y agoIt certainly is easy to make a mess of it with C/C++. It can be done well and safely but there are no guard rails. I have written this code in C and trusted it to run as intended and it did. I wouldn’t stake human lives on it but that wasn’t my requirement at the time. Val grind and other code analysis tools certainly didn’t complain and I had no memory leaks. Rust didn’t exist at the time. One specific project wrangled about 1000 worker threads, a logging thread, a network server thread, a signal processing thread, and a main control thread to the tune of a very large number of requests per second on commodity hardware. In running it for I think 4 years I had one memory leak initially that Valgrind quickly found. Could probably write that service with a lot fewer LOCs today with a language like Rust of course and with all kinds of memory safety. But at the time it worked well. Oh and it had to do all kinds of fun low level networking stuff with elevated privileges so double danger :)
- justsomeuser 6y agoI find async/await to be easier to use than alternatives. I see two categories: - A. Async/await - compiler saves and resumes functions. - B. Message based - Golang, Erlang, threads with messaging. With category A, I can use my IDE to jump to every function that is called and easily follow the computation. With category B, all of these connections happen at runtime with messages. When you have a tree of tasks all which may save/resume many times, async/await it easier to understand than launching a thread per IO event.
- jeremyjh 6y agoThis a confused notion. A useful way to think of Go and Erlang is that they automatically and transparently insert async/await each time you call a function that performs I/O. Messaging between different application tasks is completely orthogonal and can have use cases in languages with async/await as well.
- justsomeuser 6y agoI probably should have put the categories as: A. Implicit messaging using the languages function syntax (async/await). B. Direct messaging using a message passing feature of the runtime (Erlang, Golang) Note: I mean "messaging" in the context of a single OS process, that possibly has many threads (so within a single language runtime). Async/await is still implicit messaging, but it appears like a regular function call - which in my opinion is easier to understand. Using function args/return for input/output is something every developer already knows. In contrast, Erlang and Golang require you to use some type of messaging feature in addition to functions. > A useful way to think of Go and Erlang is that they automatically and transparently insert async/await each time you call a function that performs I/O The part they are missing from async/await is the ability to easily get return values without messaging, and do this recursively for a large tree of functions. E.g. getting a return value from `go x()` requires messaging, but with async/await you could do `const p = x(); const ret = (await p); // return value received at a later time with no messaging.` Both of them will require you to create some type of messaging topology to return the values (which makes your program a mixture of (regular functions + messaging features) vs async/awaits "everything looks like a function").