multithreadingasynchronousblocking

Why are we so wary of blocking threads anyway?


Ever since I started doing programming, I was always driven to consensus that thread blocking is bad - there are operations that are intrinsically asynchronous and we should create code that respects this condition. So that's how we get to the callback hell, that's how Promise API gets invented, that's how Rx appears, that's how async-await was popularized via C#, etc.

However, my language of choice has very poor support for async-await and coroutines, and I was wondering - what are the consequences of writing an iOS or Android app in a synchronous fashion by converting all asynchronous operations into synchronous via blocking?

My rationale is:

So what that I block a couple of threads - it will give me a benefit of writing completely synchronous code! So are there any other reasons why it's unacceptable to do that? I am looking for some reasons beyond the common understanding, like is there such a thing that an operating system would hate my process for blocking threads? Or maybe, this is somehow deeply inefficient?


Solution

  • You might like to consider something like Actor Model programming, or even better Communicating Sequential Processes.

    In the modern era, Actor Model programming is well catered for by libraries such as ZeroMQ (which is efficient for both inter and intra process Actor Model architectures, and extremely multiplatform). Communicating Sequential Processes is an evolution of that, and is embodied in languages like Go and Rust. You can implement CSP on top of Actor, if you wish.

    With both Actor and CSP, what one is basically doing is as you suggest; each thread or process is a strictly sequential thing, and all of them are either waiting for messages to arrive, or busy processing the last message or some other blocking operation. Taken to extremes, one can end up with a lot of context switching, but then again if one were to use async / await an awful lot then there'd be a fair bit of context switching going on anyway.

    Personally, I prefer the explicitness of both Actor or CSP; the threads that exist are the threads I started, and not ones that got generated half way throuh by some complex async programming paradigm, or failed to be allocated out of an oversubscribed pool.

    Obviously, if you create and dump threads during runtime a lot then that's going to be costly, but generally most application problems can be solved with a set of threads that are started once at program execution and don't die until the program needs to quit.

    The difference between Actor and CSP is that, in the former, message transfer between processes / threads is asynchronous, whera as in CSP they're synchronous, and are also called "execution rendezvous". In CSP, sending doesn't complete until the recipient has all the sent data. CSP is what I prefer, because it doesn't let you hide things as "latency".

    For example, suppose you have a main thread that issued 1000 fetch requests to 10 fetch threads, each of which will return a result when completed. With Actor model, you queue up the 1000 fetch requests very quickly. And eventually the results will start trickling in. With CSP, if those fetchers are falling behind the curve, the main thread gets delayed; the point is, there simply aren't enough fetchers to keep up with demand and CSP make that obvious whereas Actor model can (temporarily) disguise the issue.

    CSP also has the benefit that if you've written a circular loop that can deadlock, it will every single time no matter what. Whereass with Actor model it can all look just fine until a network switch goes a little slow...

    Anyway, that's my 2 cents worth! Both Actor and CSP are old ideas - 1970s.