
The Various Kinds of IO – Blocking, Non-Blocking, Multiplexed and Async - didibus
https://www.rubberducking.com/2018/05/the-various-kinds-of-io-blocking-non.html
======
kccqzy
One thing I'd like to add to the discussion is the difference between edge-
triggered and level-triggered notifications. Quoting from the Linux
Programming Interface (Kerrisk, 2010),

> Level-triggered notification: A file descriptor is considered to be ready if
> it is possible to perform an I/O system call without blocking.

> Edge-triggered notification: Notification is provided if there is I/O
> activity (e.g., new input) on a file descriptor since it was last monitored.

The two different types can affect the way the program is structured. For
example, the same book says that for level-triggered notification, generally
when a file descriptor is ready, only one read/write operation is performed,
and then go back to the main loop. Whereas for edge-triggered, usually I/O is
performed as much as possible, so we don't miss any more opportunities.

In practice, you usually want your file descriptors to be non-blocking, for
many reasons (like writing a large enough buffer can nevertheless block even
when the file descriptor is initially considered ready for writing), so even
for level-triggered notifications you can read/write in a loop.

Personally I believe edge-triggered notifications can make program design
slightly simpler, though I'm not exactly uncertain how much simpler. I'd
appreciate if my comment would invite a more detailed and nuanced discussion
about those two.

~~~
ajross
> In practice, you usually want your file descriptors to be non-blocking, for
> many reasons

I think I know what you're saying, but in practice this is exactly backwards.

 _Usually_ you're doing I/O for some practical reason and want to do simple,
well-defined, typically sequential processing on the results. Which is to say
blocking _is your friend_ and you shouldn't be mucking with parallel I/O
paradigms if you can at all avoid it.

~~~
xg15
> _Usually you 're doing I/O for some practical reason and want to do simple,
> well-defined, typically sequential processing on the results. Which is to
> say blocking is your friend and you shouldn't be mucking with parallel I/O
> paradigms if you can at all avoid it._

In principle yes, though it becomes annoying when your use-case evolves or you
have to miss out on otherwise obvious optimization opportunities because
they'd seriously complicate your program.

E.g., Your program is mostly sequential but _in one step_ , you'd be able to
do a bunch of requests in parallel.

I think paradigms like async/await are a step ahead to give you the best of
both worlds here: You can write your programs _as if_ your requests block, but
it still uses async IO behind the scenes - _and_ you can drop the pretense of
blocking when it makes sense at any time.

~~~
vfaronov
> _async /await are a step ahead to give you the best of both worlds here: You
> can write your programs_ as if _your requests block_

Are you thinking of “green”/M:N threading (as found e.g. in Go)?

Async/await (as found e.g. in Python) is precisely what _hinders_ the style
you describe: If your brand new I/O routine is “async colored” to take
advantage of non-blocking syscalls, you can’t easily call it from your regular
“sync colored” code without “spilling the color” all around, i.e. considerable
refactoring.

------
ioquatix
There are three main kinds of async IO.

* Event driven with callbacks (`on data`, `on error`, and so on).

* Stackless coroutines (async/await).

* Stackful coroutines (no direct semantic changes to code required).

Personally, I like stackful coroutines because the concurrency model has
minimal effect on the structure of the code, i.e. it's possible to retrofit
existing code without change.

~~~
girvo
What’s a good example implementation of stackful coroutines?

~~~
anonacct37
Would golang count?

~~~
ioquatix
Yes go would fit into the green thread bucket pretty well.

------
magnat
> On the IO completing, the OS will suspend your thread, and execute your
> callback.

On Windows, you have to put your thread to sleep to receive any callbacks [1].
If OS would suspend your thread at random point to execute a callback, that
could lead to hard-to-detect/debug deadlocks and race conditions.

[1] [https://msdn.microsoft.com/en-
us/library/windows/desktop/aa3...](https://msdn.microsoft.com/en-
us/library/windows/desktop/aa364052\(v=vs.85\).aspx)

------
erikb
It's a little bad for newcomers to define these as one being better than the
other. In reality it's a trade-off. And multiplexed/async are not really "I/O
types" but ways to handle the two I/O types. In reality everything is either
blocking or non-blocking and you always need to consider which option is
better for your current usecase. Even the most fancy of web frameworks won't
take that out of the equation.

------
kbumsik
I think there are some misleading points in a hardware perspective at intro.

> That is, gone are the days of serial communication. In that sense, all
> communications between the CPU and peripherals is therefore asynchronous at
> a hardware level.

At a hardware level, 99.99% of modern peripherals are based on serial
communication (e.g. USB, PCIe, I2C...), and these serial comminications are of
course fully asynchronous. They implement concurrent communication at a higher
level.

> Think of the simple case, where the CPU would ask the peripheral to read
> something, and it would then go in an infinite loop,

Many peripherals are logic blocks that doesn’t infinite loop internally.
Infinite loop is a kind of software techniques and they don’t need to
implement looping to do their job.

~~~
corysama
In both of your arguments you are disagreeing because you are not talking the
same thing that the author is talking about. You are talking about what the
hardware does behind the scenes. He is talking about what is going on in the
software on the surface. “serial communication” should maybe be rephrased as
“serialized communication and execution” in the application process.

------
adamnemecek
Also vectored and ninety mapped but that’s in a somewhat different category.

~~~
RonInDune
Where is vectored I/O used in practice? I'd think the requirement of using
multiple buffers for scatter/gather is not very efficient...

~~~
ramchip
Erlang iolists are an example. It means you can generate HTML from templates
as a deeply nested list of strings, and write that to a socket efficiently,
without concatenating the strings.

------
known
[http://www.kegel.com/c10k.html](http://www.kegel.com/c10k.html) FTW

------
lettergram
I actually have a little repo that has many of the ways you can have IPC
(interprocess communication):

[https://github.com/lettergram/IPC-
examples](https://github.com/lettergram/IPC-examples)

Lots of fun to play with

------
jerf
I feel like one critical misunderstanding people have is left out in between
the hardware and software discussion, which is that the kernel provides
certain abstractions, and everything else is built on top of that. Whether or
not a program is blocking or non-blocking or multiplexed or whathaveyou is an
effect depends on the _layer of abstraction_ you are looking at. It is
perfectly possible to have a programming language that is blocking that, if
you strace it, is using the non-blocking kernel calls. It is perfectly
possible to have an "async" layer under the hood and implement a "non-
blocking" layer on top of it, and then on top of that you could easily (even
accidentally!) implement a blocking layer of abstraction. It's a lot more a
continuum than people realize.

I also got the sense during the height of the Node craze that some of the
people who were very excited about it had the impression that Node had some
sort of unique access to a "non-blocking" kernel layer or something (of
course, they just weren't thinking about the kernel layer at all) that no
other languages were using. In reality, if you set something like a Go program
to a single OS thread for execution, you could mechanically translate a Go
program into something that executes in principle identically to a Node
program at the assembler level, at least in terms of what events come in and
what sort of high-level code gets executed in response (vast differences in
the literal instruction stream, of course). The converted Go would have a lot
more "event handlers" than you'd expect because Go programs also break at
function calls and a few other places (as the compiler is doing it it's easy
to have lots of "event break points" that you'd never write by hand), but the
compiler essentially converts the "blocking code" that uses lots of threads
into "non-blocking event-based code". The difference between the two is not at
the execution layer; it's at the layer you're programming. In fact pretty much
everything nowadays is "non-blocking event-based code" at the execution layer,
because the high-powered kernel functions that make that efficient implement
event-based code, so anything else you find at a programming-language-level
must be converting that abstraction into the language's abstraction. Even
using kernel threads still turns into non-blocking event-based code under the
hood, if you dig far enough in.

(A more clear example of how critical the _layer of abstraction_ you are
looking at is IMHO is the distinction between immutable and mutable. You can
implement an immutable abstraction layer on top of mutable storage, which
given that our hardware is based on mutable storage, all immutability in
programming languages necessarily comes from. You can implement a mutable
abstraction on top of an immutable layer with a worst-case penalty of O(log
n), as in the worst case you represent memory as a immutable tree of bytes and
then mutate as you would normally. Whether or not something is "mutable" or
"immutable" critically depends on layer of abstraction you are looking at; it
does not have a non-contingent answer, or, if it does, since our hardware is
mutable the answer is that immutability does not exist at all. But while true
in a sense, that answer is not as _useful_ as one that is contingent on the
abstraction layer being examined.)

~~~
wejick
Yup basically under the hood I saw golang also using event loop for io, but I
never have chance to see the source code to confirm it. have some write up on
this, not directly related tho. [https://wejick.wordpress.com/2017/06/04/tcp-
socket-implement...](https://wejick.wordpress.com/2017/06/04/tcp-socket-
implementation-on-golang/)

