Ok, let me try to restate the 2 questions:
- Does OS actively preempt a thread as soon as it starts blocking, and never return to the thread until blocking is done? I feel that the OS has the information about disk IO and network IO so it should have enough information to do so.
- If the OS can eliminate the CPU idle time by switching to another thread, do we really need asynchronous programming?
CodePudding user response:
Therefore, even though the thread is blocking, the CPU is not blocking but running other threads.
Yes, that's correct.
If my understanding above is correct, what is the value of asynchronous programming?
It's useful if you want your program to make progress on multiple tasks simultaneously.
Of course you could get the same effect by explicitly spawning multiple threads yourself, and having each of them work on a separate task (including any blocking calls), and that would work as well, but then you have to explicitly manage all those threads which can be a bit of a pain to get right. (inter-thread synchronization/communication can be tricky, and in particular the case where you want to cancel an operation is difficult to implement well if one or more of your threads is blocked inside a blocking I/O call and thus can't be easily persuaded to exit quickly -- then your other threads may have to wait a long time, possibly forever, before they can join()
that thread and terminate safely)
CodePudding user response:
The re-invigoration of asynchronous programming has little to do with OS/kernel architecture to date. OSes have blocked the way you describe since the 1960s; although the reason has changed somewhat.
In early systems, efficiency was measured by useful CPU cycles; so switching tasks when one was blocked was a natural act.
Modern systems architecture is frequently addressing how to keep the CPUs occupied; for example if there are 800 CPUs but 20 tasks; then 780 CPUs have nothing to do.
As a concrete example, a program to sum all the bytes of a file might look a bit like:
for (count=0; (c = getchar()) != EOF; count = c) {}
A multi-threaded version, for performance increase might look like:
for (n=0; n < NCPU; n ) {
if (threadfork() == 0) {
offset_t o = n* (file_size / NCPU);
offset_t l = (file_size / NCPU);
for (count = 0; l-- && pread(fd, &c, 1, o) == 1; count = c) {}
threadexit(count);
}
}
for (n=0; n < NCPU; n ) {
threadwait(&temp);
total = temp;
}
return total;
which is a bit grim, both because it is complex, and probably has inconsistent speed-ups. In comparison the function:
int GetSum(char *data, int len) {
int count = 0;
while (len--) {
count = *data ;
}
return count;
}
I could construct a sort of dispatcher which, when a lump of file data became available in ram, invoked GetSum() on it, queuing its return value for later accumulation. This dispatcher could invest in familiarity with optimal i/o patterns etc.. since it may be applicable to many problems; and the programmer has a considerably simpler job to do.
Even without that sort of native support; I could mmap(2) the file, then dispatch many threads to just touch a page, then invoke GetSum on that page. This would effectively emulate an asynchronous model in a plain old unix-y framework.
Of course nothing is quite that easy; even a progress bar in a dispatch-oriented asynchronous model is dubious at best (not that the 1950s- based sequential ones were anything to write home about ). Communicating errors is also cumbersome; and because you use asynch to direct maximum cpu resources at yourself, you need to minimize synchronization operations (duh, async :).
Async has a lot of possibilities; but it really needs languages with defacto async support, not as an aspirational nod from the latest du jour standard of some rickety language.