ブロッキングI/O, ノンブロッキングI/O, 同期, 非同期

webserverを作る過程でこの辺について深ぼったのでメモ
まず言葉の定義はこちらを用いる。
これはいわゆるPOSIXの仕様書で以下である
POSIX(ポジックス[1][2][3][注釈 1]、英: Portable Operating System Interface)は、オペレーティングシステム (OS) の標準的なインタフェースおよび環境を定義するIEEE規格である[4]。ソースコードレベルでの移植性の高いアプリケーションソフトウェアの開発を容易にすることを目的として、主にUNIX系のOSに関して、各OSが共通して持つべきコマンドラインインタプリタ(シェル)、共通のユーティリティプログラム、およびアプリケーションプログラミングインタフェース (API) について定めている[4]。POSIX仕様に準拠したソースコードは、POSIX準拠OSであればどれでも動作させることができる。アプリケーション開発者とシステム実装者の両方から利用されることを意図している。

ブロッキングとノンブロッキング
これらの定義の前にopen file description
の定義を見てみる
Open file description:
A record of how a process or group of processes is accessing a file. Each file descriptor refers to exactly one open file description, but an open file description can be referred to by more than one file descriptor. The file offset, file status, and file access modes are attributes of an open file description.
プロセスまたはプロセスグループがファイルにアクセスする方法を記録したもの。各ファイルディスクリプタは、正確に1つのオープンファイル記述を参照するが、オープンファイル記述は、複数のファイルディスクリプタによって参照されることができる。ファイルオフセット、ファイルステータス、およびファイルアクセスモードは、オープンファイル記述の属性である。(deepl直訳)
なんのこっちゃわからんかったが、こちらのページがわかりやすかった。
つまりopen file descriptionとはfile descriptorで参照されるポインタである。file descriptorは一つのopen file descriptor を参照するが逆は複数ある。つまりdupしてもよいし、他のプロセスから参照するなどしてもよいということ。
例えば、dup(0)などをするとfdは複製され違う値がつくられるが参照さきは同じ標準入力となる。
Blocking:
A property of an open file description that causes function calls associated with it to wait for the requested action to be performed before returning.
open file descriptionの情報を操作する関数が呼び出されたとき、リクエストされたアクションが実行されるのを待ってから処理が戻るもの
Non-Blocking
A property of an open file description that causes function calls involving it to return without delay when it is detected that the requested action associated with the function call cannot be completed without unknown delay.
open file descriptionの情報を操作する関数が呼び出されたときに、未知の遅延なしに関数の完了が行えないことがわかった場合、遅延なしに関数の処理を戻す
つまりreadなどを呼びたいときに、何らかの遅延が存在することがわかったらすぐ処理が終わるのがNon-Blocking、いつまでも処理が終わらないのがBlocking

同期 vs 非同期
同期I/O操作: Synchronous I/O Operation
An I/O operation that causes the thread requesting the I/O to be blocked from further use of the processor until that I/O operation completes.
I/O操作の際に、その操作が完了するまでI/O操作を行ったプロセッサの使用がブロックされるということ。
非同期I/O操作: Asynchronous I/O Operation
An I/O operation that does not of itself cause the thread requesting the I/O to be blocked from further use of the processor.
This implies that the process and the I/O operation may be running concurrently.
I/O操作の際に、その操作が完了するまでI/O操作を行ったプロセッサの使用がブロックされないということ。
これは、プロセスとI/Oオペレーションが同時に実行される可能性があることを意味する
つまりI/O操作をするときに、処理が完了するまで他の処理がブロックされるのが同期I/O操作。
I/O操作をするときに、たとえI/O操作の処理が完了してなくても他の処理をし続けることができるのが非同期I/O操作となっている

ちなみにこのI/O操作の定義が見つからなかった。
なのでI/O操作とはfile descriptionを操作する各種システムコールのことだと判断した

上の4つの定義を4象限に分けて説明しているのがこちらの記事になる。
この4つの象限に対して考える。
同期ブロッキングI/O: Synchronous blocking I/O
これは標準形のI/O操作である。read関数をsocket_fdに接続して何も処理が帰らないと、以下の処理がpendingする。このようなものが同期ブロッキング操作である。
同期ノンブロッキングI/O: Synchronous non-blocking I/O
これはfdに対してのオプションでO_NONBLOCKを設定することでsocket通信に対する操作がブロックされたとき戻り値-1, errno EAGAIN or EWOULDBLOCKを返すようにすることで実現する
[open]https://linuxjm.osdn.jp/html/LDP_man-pages/man2/open.2.html
[socketのread]https://www.ibm.com/docs/ja/zos/2.3.0?topic=functions-read-read-from-file-socket
非同期ブロッキングI/O: Asynchronous blocking I/O
非同期というのは上述の通り、I/O操作が他の処理をブロックしないということ。これはselectを使って実現できる。
selectは例えば以下のようにreadするファイルディスクリプタが複数存在するときに、fd_1の操作が終わらないと、fd_2が読み込めないといった同期ブロッキングI/Oの問題を解決する。
read(fd_1)
read(fd_2)
以下の図を見ると、ReadがNonBlockingでEAGAINなどのエラーコードが帰ってきたのをみてからSelectのブロッキングがはじまり、Readの操作が準備完了になった状態を判断したら、Read()のシステムコールが走るといった状態である。
つまりReadはI/O操作とし、Selectはopen file descriptionの情報の操作となるので
- I/O操作は他のプロセスの処理に影響を与えない→非同期
- open file descriptionの操作→ブロッキング
このようになり非同期ブロッキング操作
ということになる
各種I/O操作(read/write)はもしpendingになる場合は、NON-BLOCKINGのエラーコードを返却して処理をブロックしないことから非同期扱いになっている。

ここでよくわからないのは**ではNon-BlockingのRead/Writeなどのシステムコールは非同期ではないのかということである。
なぜならerrorが変えることで処理を終了するのであれば、他の処理に影響はないように感じるからだ。
そこで同期NonBlockingを説明している以下の図を示す
ここに記載されているのは例えエラーが返されてもループにして何回もRead()を呼び出しているという例である。
Readを呼び出す後続の処理がReadで読み込んだ値に対して影響を受ける処理ならば(というかRead読んでいる時点でそれは当たり前ではある)、I/O操作が終了するまで、他の処理をBlockしているというふうに言い換えても良いのかもしれない。
ということから、結局selectのように複数のI/O操作を同時に扱うことができるシステムコールではない限り、非同期ということにはならないと、このページは言っている気がする

まとめ
Blocking vs Non-Blocking
これはopen file descriptionを扱うシステムコールに関するプロセスのブロックの話
file descriptionの操作によって処理を待っているときにプロセスの処理が止まってしまうのがBlocking
処理待ちの場合はエラーを返して、処理をpendingさせないようにしているのがNon-Blocking
同期 vs 非同期
I/O操作の話。I/O操作を行うときに他の処理をブロックするのが同期
処理をブロックせずに他の処理を行うことができるのが非同期となる。
同期 vs 非同期とBlocking vs Non-Blockingは同じようなことを言っている気がして混乱してしまうが、IBMのページが正しいことをいっているならこのように解釈することができるとしか言えない