非同期I/OやノンブロッキングI/O及びI/Oの多重化について

2017年5月20日追記

本エントリはI/OのOperationとCompletionおよびデータ整合性を混ぜてまとめた一部誤った定義になっているので、正確な定義を日本語で知りたい方は下記にリンクしたエントリを読むことをおすすめします。

非同期とノンブロッキングとあと何か

 


Apache2.4.1のevent_mpmやnginx及びnodde.jsのアーキテクチャを考える上で、非同期I/OやノンブロッキングI/O、I/Oの多重化に関してある程度正確な理解が必要だと思ったのでまとめておく。

ここで「ある程度」といったのは、非同期を表すAsynchronousとノンブロッキングのnon-blockingは曖昧に使われる場合が多いからだ。まず、英語の場合でも、Asynchronous I/Oとnon-blocking I/Oは同義とされていたりする。

Asynchronous I/O, or non-blocking I/O, is a form of input/output processing that permits other processing to continue before the transmission has finished.

参照:Asynchronous I/O

しかし、ここはあえて、UNIXネットワークプログラミング Vol.1やBoost application performance using asynchronous I/Oにのっとって、非同期I/OやノンブロッキングI/O、及び、I/Oの多重化に関する定義を確認しておく。

  • ブロッキングI/O

データ処理が完了するまで待つ

  • ノンブロッキングI/O

データ処理の完了を待たずに他の処理を行う

一方で、同期、非同期I/Oは以下となる。

  • synchronous I/O Operation

データ処理の入出力が可能になった時点で通知

その後に同期的にデータの転送が必要。その処理をブロックあるいはノンブロック(別の処理をしつつ定期的に転送完了をチェックする)に行うかは自由であり、データの整合性は含まない

参照:http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_374

  • Asynchronous I/O Operation

データ処理の入出力が完了した時点で通知

非同期なので読込の場合は通知のあった段階で既にデータの転送は完了(I/O Completion)しバッファ内にデータがある。シグナルやコールバックによる通知があるまではユーザスペースでその他の処理が可能であるため、基本的にはノンブロック。別にやりたければ通知があるまでブロックしても良い、その辺りは自由

参照:http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_28

また、別の視点でややこしいI/Oの多重化については以下となる。

  • I/Oの多重化

I/O可能になったfdを通知(I/OはブロッキングI/O)

上記のように定義できる。
Boost application performance using asynchronous I/Oにおける分類をみていく。(図を引用させていただきました)

 

まずは、以下が基本的なI/Oのblockingかつsynchronousのフローだ。read()でblockingされている。

続いて、blockingかつAsynchronousと分類「されている」多重I/Oのフローは以下となる。

この資料にのっとると、「Figure 1. Simplified matrix of basic Linux I/O models」の「i/O mulitplexing」は「Asynchronous I/O」に属しているが、本来、「Asynchronous I/O」に属さないように思っている。なぜなら、I/Oの多重化におけるselect()はI/O可能になるまで待たされる(タイムアウト処理は可能)ためblockingであるが、入出力が可能になった段階(後にreadが必要な状態)で通知されるので、通知の段階で既にバッファ内にデータがあるはずのAsynchronous I/Oには当てはまらないように思う。これはまさに同期I/Oの特徴であり、自らI/Oの状態を見に行かなければならないノンブロッキングな処理をブロックして自動化して効率を上げた、blockingでsynchronousなI/Oのmultiplexing化という処理のように解釈できる。

A synchronous I/O operation does not imply synchronized I/O data integrity completion or synchronized I/O file integrity completion.

参照:http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap03.html#tag_03_29

blockingかつAsynchronousに当てはまるとすれば、後述するlibevとlibeioの組み合わせ時だろうか。

(ここでいうAsynchronousがAsynchronous I/Oという意味でなければどういう意味をさしているのだろう、ここ誰かうまく説明できる方いませんか?)

I/Oのためのライブラリでlibevやlibeioがあるが、イベントループのためのライブラリであるlibevは内部でselect()を呼び出しているため、libevはblockingかつsynchoronousと考えられる。イベントループとは、ループしながらI/Oを監視し、I/Oが可能になったイベント等を通知する機能のことだ。一方で、non-blockingかつAsynchronousな特徴を持つlibeioは、イベント通知時にすでにバッファにデータが入っており、上記のAsynchronous I/Oの定義にマッチする。

Webサーバの視点で考えた時は、ネットワークI/Oとディスク(ファイル)I/Oが組み合わせて考える必要がある。順番としては、リクエストを受けた段階でネットワークreadが生じ、コンテンツを読むタイミングでディスクreadが生じ、レスポンスを返す段階でネットワークwriteが生じる。そのため、libevはネットワークI/Oで利用される場合が多く、blockingかつsynchronousなI/Oを行うため、ネットワークI/OでI/Oブロッキングが生じる。そのため、ディスクI/O部分をlibeioと組み合わせて行った場合でも、libeioはnon-blockingかつasynchronousであるが、ネットワークI/OにおけるlibevによるブロッキングI/Oがボトルネックとなる。

なので、Webサーバ上でnon-blockingかつasynchronousな特性を生かす実装をするためには、POSIX AIOでネットワークI/Oを行い、libaioでディスクI/Oを行うのが良いのではないかと考える。なぜなら、libaioはfdとしてソケットを扱えないからだ。

以上が、簡単ではあるが同期/非同期、blocking/non-blockinに関する現時点の認識である。何か間違っていることがあれば、是非教えて頂ければと思っています。