Question

Java author mentions: Threads can simplify the development of complex systems by turning complicated asynchronous code into simpler straight-line code.

Again the book says: Threads make it easier to model how humans work and interact, by turning asynchronous workflows into mostly sequential ones. They can also turn otherwise convoluted code into straight line code that is easier to write, read, and maintain.

Am unclear with above two points.


My understanding is,

1) Multi-threading can be used for either:

  • Mutually exclusive tasks, CPU bound. This is parallel programming.

  • Concurrent tasks, CPU bound. Involves synchronization mechanism.

2) Asynchronous code(with Event loop) is relevant for mutually exclusive tasks that are IO bound. This is single threaded code.

Edit after reading comments:

Async programming is like, Chess champion, Polgar(is CPU) playing with multiple amateur chess players(are tasks). Nature of this game is amateur(task) need some time before Polgar(CPU) can come back with next move.

How is Async implemented?

  • Task capability – Suspend & Resume
  • Event loop scheduling these tasks

There is no more than one thread involved.

Non-blocking IO basically involves select() UNIX system call, under the hood. For example, python code below,

def runForever(self):
        while True:
            readers, _, _ = select.select(self.readers, [], [])
            for reader in readers:
                reader.onRead()

Non-blocking IO is completely different programming model compared to Async IO. But,

Can a programming model unify all three, non-blocking, asynchronous nature & multithread support? Yes. Chrome V8 engine is an example, shown in this diagram.


How multi-threading help problems solved by asynchronous code, to provide straight line coding?

Was it helpful?

Solution

Would you rather write:

void foo(Channel<Channel<int>> chan1) {
    chan1.read((Channel<int> chan2) -> {
        chan2.read((int result) -> {
            bar(result);
        });
    });
}

or:

void foo(Channel<Channel<int>> chan1) {
    Channel<int> chan2 = chan1.read();
    int result = chan2.read();
    bar(result);
}

or even:

void foo(Channel<Channel<int>> chan1) {
    bar(chan1.read().read());
}

?

Let alone the hell that the first example would be in Java pre-lambdas. Things like futures and promises can make the first example look a little nicer, but won't eliminate the fundamental loss of stack discipline. This impacts the scoping of variables and how exceptions propagate. try/catch no longer works in a meaningful way. More complicated examples than the above clearly illustrate that futures and promises do a poor job of recovering a "straight-line code" style.

Asynchronous programming is a form of concurrent programming. They have basically the same use-cases, and in particular, they both are fine for IO-bound workloads. They are even dual to each other. And in particular related to that, there's no need for threads to be OS-level processes, hence the "lightweight" or "green" threads supported by many languages which often provide no parallelism (as in utilization of multiple cores).

OTHER TIPS

Read this first:

Eric Lippert has already written an excellent answer on Stack Overflow that should also answer your question: What is the difference between asynchronous programming and multithreading?


TL;DR

Your hunch is right; multi-threading wouldn't improve the performance aspects of IO-bound tasks.

What the Java author (as quoted by OP) says is, while it wouldn't improve performance, it reduces the complexity of your code.

Different goals, different conclusions.


My answer focuses on the use of terminology and the issue of confusion.


There are several levels of "can". The first one is the least generalisable and the third one is the most generalisable.

  • That it is possible at all
    • (i.e. without considering merits and efforts)
  • That it is advantageous to do so in terms of a specific criteria
    • (e.g. able to shorten the time to completion by leveraging unused CPU cores)
  • That it is generally recommended for the given situations after a wide range of criteria have been considered
    • (i.e. there is an accompanying list of criteria considered)
    • (but, it also makes a lot of assumptions about "the general case / needs", which means someone with an unusual requirement may find it not applicable.)

When you mention "mutually-exclusive tasks", a better way to say that is tasks that can be independently executed, and does not obstruct and/or interfere (harm) each other when executed simultaneously (as in multiple CPU cores) or concurrently ("overlapped", or "asynchronously", or "task-switching").


It is always possible, and is usually advantageous, to have independently executable tasks executed over multiple CPUs. The exception to this rule is if the tasks are too small, then the overheads (mostly the wait time due to handing off tasks and results across CPUs) will ruin the savings.


Asynchronous is an adjective that roughly means punctuated. It means that a task is not continuously executing. It switches between executing and pausing (blocking). In general it is gradually progressing toward the goal (completion).

Asynchronous can refer to:

  • The coding style,
  • Syntactic and code-generation features provided by a programming language,
  • The overall manner in which a higher-level task is executed, a fact which has to be judged subjectively.

When asynchronous programming is done on a programming language that does not provide the best syntactic sugar for it, the result is more complicated code which is harder to understand, maintain, and troubleshoot. Therefore, it comes with the advice (or, a break from the general advice) that, if the code complexity is too high, then it is advisable for one to abandon the asynchronous programming attempt and fall back to a synchronous coding style.

If the language provides very good syntactic sugar for it, then a lot of programming can be done in asynchronous programming style without suffering the code complexity.


Asynchronous code is not required to be single-threaded. In fact, on C# you can combine asynchronous programming and multithreading(*), as explained in Eric Lippert's answer (see top of answer for the link).

(*) It just creates a "how do I get back to the caller" / "code executing on the wrong thread" hell. For this reason it is recommended to have one "master thread" in charge of delegating tasks, and this "master thread" will implement an event loop, which is the mechanism through which it will receive procedure invocation, communication, and completion from other tasks or worker threads.


An architecture that is asynchronous and single-threaded may sometimes be chosen because of technical limitations of one or more libraries or frameworks it uses. For example, when programming on Windows, the GUI is usually single-threaded, which means it is not thread-safe to be invoked from a thread that is not the main-thread.


The word "I/O bound" is usually a misnomer. It basically means "tasks that take time to finish yet aren't CPU-bound."

As an example, a task that is sometimes said to be "I/O bound" involves just a little bit of data transmission. For example, data is being crunched on a remote server; the local computer's task is merely waiting for the remote server saying "done".


Licensed under: CC-BY-SA with attribution
scroll top