Remix.run Logo
toast0 3 days ago

Sorry, I kind of spaced on reading the article, but from BEAM land, everything is built around concurrent processes with asynchronous messaging.

You can send a message to something else and wait for the response immediately if you want to write in a more blocking style. And you can write a function that does bot the sending a message and the waiting, so you don't really need to think about it if you don't want to. All I/O pretty much feels the same way, although you get into back pressure with some I/O where if there's a queue you can opt to fail immediately or block your process until the send fits in the queue.

The underlying reality is that your processes don't actually block, BEAM processes are essentialy green threads that are executed by a scheduler (which is an OS thread), so blocking things become yields, and the VM also checks if it should yield at every function call. BEAM is built around functional languages, so it lacks loops and looping is handled by recursive function calls, so a process must make a function call in a finite amount of code, and so BEAM's green threading is effectively pre-emptive.

The end result of all this is you can spawn as many processes as you like (i've operated systems with one process per client connection, and millions of client connections per node). And you can write most of your code like normal imperitive blocking code. Sometimes you do want to separate out sending messages and receiving responses, and you can easily do that too. This is way nicer than languages with async/await, IMHO; there's no trickyness where calling a blocking function from a async context breaks scheduling, and calling an async function from a non-async context may not be possible... You do still have the possibility of a function blocking when you didn't expect it to, but it will only block the process that called it and transitively, those processes that are waiting for messages from the now blocked process.

Java's Project Loom seems like it will get to a pretty similar place, eventually. But I've seen articles about some hurdles on the way; there's some things that still actually block a thread rather than being (magically) changed to yielding.

Again, IMHO, people didn't build async/await because it is good. They built it because threads were unavailable (Javascript) or to work around the inability to run as many threads as would make the code simple. If you could spawn a million OS threads without worrying about resource use, only constrained languages would have async/await. But OS threads are too heavy to spawn so many, and too heavy to regularly spawn and let die for ephemeral tasks.