IMHO all the rage on async comes from an era when interpreted languages where the main trend as they demonstrably increased developer productivity --I'm looking at you, Rails, Django. Those platforms were not designed for runtime speed, so it made sense to push the bottleneck to the most inmediate backend system in the chain (i.e.: your trusty database, which is much faster than your agile framework of choice, remember the discussions of ORMs versus pure SQL?)
Then there came async frameworks a la Node, Twisted et al. and changed everything. Again in my opinion, async code is harder to reason about versus synchronous code.
Things to keep in mind:
- Are you really working at scale? Does the arguably added complexity of async benefit your particular use case? Specially when SPA technologies allow to build simpler backend for frontend systems (pure API, no HTML rendering). And not only regarding pure operational performance, Rails is still impossibly hard to beat when it comes to productivity.
- New players like Go, Rust have async capabilities but you dont necessarily need to use them to perform closer to native speed, hence becoming simpler solutions than Node, Ruby, or Python. Guess that also applies to old dogs with new tricks in the JVM (Micronaut, Quarkus...)
> Then there came async frameworks a la Node, Twisted et al. and changed everything. Again in my opinion, async code is harder to reason about versus synchronous code.
One change here which has also made this less important is the trend toward serverless/microservice architecture. In a world where you were running your MVP on a single VPS instance somewhere, and you were running a monolithic webserver which handled everything from request parsing to business logic, then moving from one-thread-per-request to a runloop implementation represented a potentially huge win in terms of performance.
But nowadays we largely work with more atomized bits of logic: business logic is implemented in terms of small, self-contained operations, and the problems of scale are delegated to some other system or orchestration framework. In that world it matters much less if your database access is blocking a thread or not, because throughput issues can be solved at a different level.
> IMHO all the rage on async comes from an era when interpreted languages where the main trend as they demonstrably increased developer productivity
I disagree.
Although it supported (cooperative) multithreading, Windows 3.1 applications typically had one thread that ran a message loop, invariably written in C or C++. Same with MacOS applications since 1984, except at first it was Pascal and 68000 assembly. CICS was originally single-threaded, running transactions written in System/370 assembler and later COBOL, until they bolted multithreading onto it in order to take advantage of the new SMP mainframes IBM was building. thttpd is from 1998 and is of course written in C; see http://www.acme.com/software/thttpd/benchmarks.html. One of the biggest changes in web-server software over the last ten years has been the move away from Apache to nginx, which is of course event-driven and written in C.
> async code is harder to reason about versus synchronous code.
Well, this is surely true. But generally the choice isn't async code or sync code; it's async code or multithreading. And async code is much easier to reason about than threads-and-locks code.
When you can hack it, a better approach is multiprocessing, also known as message passing, where each thread has its own private memory space, so it looks just like sync code. That's how Unix was built (though not just one but several broken threads models got bolted onto it later), it's how Erlang works, and it's the model adopted for JS in Web Workers. It has some performance cost. (Transactions, as in CICS or the Haskell STM, are another appealing option.)
Rust offers a really interesting alternative here: it statically proves that it's impossible for multiple threads to share access to writable memory (except when you cross you fingers and say "unsafe"), so in effect the threads are compiler-enforced multiple processes, but with the ability to dynamically freeze data so you can start sharing it between those processes.
From my point of view, what happened is that preemptive shared-memory multithreading with locks became a lot more popular in the 1990s and 2000s, driven by a rise in popularity of two systems originally developed for microcomputers without memory protection: Win16 (later Win32, Win64) and Java. Real-time control systems like the THERAC-25 control system have always used preemptive shared-memory multithreading, invariably with locks. (In the minimal case they use interrupt handlers rather than full-fledged multiple threads, but those have the same issue.) With the advantage of historical perspective, we can now see that it was a mistake to extend that programming paradigm to things like GUIs and web servers, but to some extent we're stuck with it for legacy systems like the JVM, Win64, and pthreads.
For new systems, though, we can use async, or multiprocessing, or transactions, or Rust's borrow checker, or a mixture.
This is a great point to observe, thanks. My commentary is biased towards web development
> But generally the choice isn't async code or sync code; it's async code or multithreading.
So true! years ago I worked with CORBA on top of ACE's libReactor and the result had an interesting mixture of the worst of the worlds of async and threads-and-locks ;)
As for web development, JS has been async since day 1. For a little while Opera was multithreading it, but that was really hard to program on, because they didn't even offer locks. And Perl was not normally multithreaded, but before POE it wasn't async either, but rather multiprocess. msql was async, as was FastCGI, originally. So I'm not sure I agree even within the scope of webdev history. Maybe you got started late when lots of people were already using Java?
Sure! I got fed up with all that multithreading Vietnam and cut my web teeth with Rails in '05 and as such my focus has been on the business side of the applications you can build on top of a lot of software pieces that may or may not be async/MT or whatever.
For example we ran a lot of Rails code on top of FastCGI with lighttpd but as far as I can remember the Rails code we wrote wasnt affected by the fact that FastCGI was async.
Then there came async frameworks a la Node, Twisted et al. and changed everything. Again in my opinion, async code is harder to reason about versus synchronous code.
Things to keep in mind:
- Are you really working at scale? Does the arguably added complexity of async benefit your particular use case? Specially when SPA technologies allow to build simpler backend for frontend systems (pure API, no HTML rendering). And not only regarding pure operational performance, Rails is still impossibly hard to beat when it comes to productivity.
- New players like Go, Rust have async capabilities but you dont necessarily need to use them to perform closer to native speed, hence becoming simpler solutions than Node, Ruby, or Python. Guess that also applies to old dogs with new tricks in the JVM (Micronaut, Quarkus...)