You can disagree all you want. Historical facts prove otherwise. Since
ancient days, the simplest task on Unixes were done as pipelined tasks, as
multiple execution contexts.
$ sort <file | uniq -c
That dates back decades.From the earlier days, shells were explicitly
designed to execute pipelined commands. Pipelined commands were their main
feature. All of this is pretty much I/O bound. Multiple execution threads.
Because of that, and battle-tested over decades, Unix was tuned to
effortlessly implement tasks that use multiple processes working in
parallel. Threads were just the next evolution, dropping most of what little
was left, in terms of per-execution context overhead. Linux inherited Unix's
legacy, and orientation, towards low-overhead multiple execution threads.
All the historical network server processes on Unix, then Linux, were multi-
processor based. Even though select() existed pretty much since 4.2 BSD
days, it was virtually unheard of for a network server to be a monolithic
process, multiplexing for its clients using select(). An incoming network
connection starts a new process, just for that network connection. Didn't
matter what network service it was. SMTP, finger, login, or what, it was all
one process per connection.
There was a reason for that. It didn't take an Einstein to figure out that
with a monolithic server, once it picked up an I/O event it had to do what
that event needs to do, and nothing else can happen until that's done. Even
if another client blurted out a packet, too bad, so sad. It needs to wait
until the current I/O event's processing is done. It made a lot more sense
to simply have another available CPU, not being busy with anything, run with
it. And, goshdarndammit, if that socket had a different execution thread
sucking from it, why, that spare CPU will know exactly what to do.
So, monolithic processes were rare. They are a little bit more common today
than before, but they are still a rarity. And, today, even network servers
that kick off multiple processes per client can be found. So, sorry, but
facts disagree: multiple execution contexts, either as standalone processes
or lightweight intra-process threads rule the roost in I/O bound contexts.
Linux inherits Unix's legacy in this respect, and the differences between
processes and threads, on Linux, are mostly cosmetic. clone() creates both
of them. You just specify which part of the parent process (virtual memory
map, file descriptors, filesystem namespace etc…) are shared with the new
execution context, and that's pretty much.
I can understand why coming from a MS-Windows background makes someone frown
at execution threads, unless all they do is stay away from the operating
system, and work entirely on their own, spinning the CPU. The only reason
stuff like IOCP exists on Windows is because windows sucks at multi-
threading and multi-processing. It always sucked, and will likely always
suck.
But that's not the case with the real world out there. Being able to have a
clean, encapsulated, logical execution thread, always busy with the forward
march of progress from the start pointing to the finish line, without being
forced into contorting into an event/dispatch based framework, results in
smaller, leaner code without all that event-based/dispatching cruft. It
doesn't matter which one of the file descriptors is now ready for I/O. It no
longer matters. The OS kernel already figured it out, and there's no good
reason for userspace to piss away more CPU cycles doing exactly the same
thing. The OS kernel knows which I/O is done, and which thread is waiting on
it, and it already knows whose ass to kick into gear.
A such, execution threads are perfect for I/O-based load. It is no wonder
that Linux rules the roost in TOP500 space. It's a dirty little secret that
all those supercomputers are just a bunch of machines with optimized
networking tying hundreds of thousands threads together. Yes, threads.
Gee whiz, how can that possibly happen?