これは ACCESS Advent Calendar の最終日の記事です。
僕は株式会社 ACCESS 開発本部 IoT 開発1課の三原と申します。
今日は POSIX の非同期 I/O について述べさせていただきます。
POSIX の非同期 I/O はイケてないと言われますが……
非同期 I/O って夢のある言葉です。不要な時は寝てて、必要になったら起床して仕事して、仕事が終われば寝る。無駄のない動きの理想です。
POSIX には非同期 I/O が定義され、各種 OS で使用できます。ところが…… 正直、使っている人を見ることはほとんどありません。POSIX の非同期 I/O を使うくらいなら同期 I/O を使う、設計が「イケてない」、そう言われます。
不要な時は寝てて、必要になったら起床して仕事して、仕事が終われば寝る、そんな API が作られたら使うと皆さんおっしゃいます。
その声に僕は疑問を持ちます。そんな非同期 I/O 、現実に実装できるんですか?
仕事を実行した後、結果を受けて反応するにはどうしましょう。反応する、そこに大きな問題があるんです。
既存のスレッドに非同期に通知する方法はない
まず考えるのは、動いているプログラムに通知してもらうことです。POSIX なら動作の単位はスレッドです。さて既存のスレッドに通知するにはどうしましょう?
POSIX API は C 言語で使用できなければいけないという絶対の決まりがあります。
C 言語の特性について拙筆で述べさせていただいたのですが、**C 言語のスレッドは、変数も関数も、自分が使うと決めたものだけを、自分が使うと決めた順序でしか、参照しません。自分が見たいものしか見ません。**処理の順序は決まっていて例外もありません。
ここで人間を思い浮かべてみましょう。他の人をからかって、こんなことをしたことはありませんか? 背中をつついて、相手が振り返ったところで頬に指をあてる。他愛ないいたずらです。人間は背中をつついて気付かせることができます。経験から、人間は、何か実体があると、外部から刺激して気づかせることができると考えます。
しかし C 言語のスレッドの背中をつつくことはできません。C 言語のスレッドに気づいてもらうには、あらかじめ「ここを見て」と指図したうえで、指示した場所に情報を書き込むしかないのです。
スレッドで動いているプログラムが自発的に結果を見に行くなら、同期 I/O を使っても同じことができます。非同期 I/O でしかできないことがなくなるんです。
既存のスレッドに通知しようとしたら非同期 I/O の意味がなくなるなら、処理の流れを新たに作ることを考えるかもしれません。POSIX なら割込みか新規スレッド生成になります。さて、うまくいくでしょうか?
割込みは処理の制限が多い
POSIX の割込みについては JPCERT に記事があります。
SIG30-C. シグナルハンドラ内では非同期安全な関数のみを呼び出す
割込みを受けたシグナルハンドラの中は、通常のスレッドとは異なる環境であり、呼び出せる関数、平たく言えば機能が大きく制限されます。制限を無視して関数を呼び出せば、動作は未定義、つまりどんなバグが発生してもアプリケーションの責任です。それだと脆弱性が生じるので JPCERT が警告しているんです。
処理に必要な関数が呼び出せなければ、通常のスレッドに処理してもらうしかありません。では、どうやってスレッドに通知しましょう? ここで先の問題に戻ってしまいます。
シグナルハンドラ内に処理を書くというのは時折使われる手法ですが、汎用的にはできないので、広くお勧めできません。
スレッド生成すれば I/O 自体より処理が重い
割込みで制限があるならスレッドを生成すれば、という話になります。
POSIX でスレッドを生成するのは極めて重い処理です。I/O 自体より重い処理です。
I/O のイベントが発火するたびにスレッドを生成していたのでは、鈍重なプログラムとなってしまいます。
スレッド同期が問題になる前に、性能面で実用になりません。
POSIX というプラットフォームの性格上、イベントループを強制できない
イベントへの対応が C 言語の関数呼び出しだったらどうでしょう?
十分軽量です。 libev という広く使われているイベント処理フレームワークもあります。
POSIX もそうすればいいとおっしゃるかもしれませんが代償があります。アプリケーションプログラマにイベントループのアーキテクチャを強制することになるんです。
iOS や Windows の GUI アプリならイベントループのアーキテクチャが決まっていても文句は言われませんが、POSIX というプラットフォームは利用者が違います。特定のイベントループのアーキテクチャを強制すれば使ってもらえません。
結果
POSIX の非同期 I/O はイケてないと言われます。しかし、**C 言語の実行モデルが決められて、POSIX の割込みモデルと Pthreads モデルが決められて、イベントループのアーキテクチャを強制できなければ、イケてる非同期 I/O は実装できないんです。**C 言語の実装モデルの決まりとイベントループを強制できない点について気づかない人が多いです。
POSIX のプラットフォームレベルで設計される非同期 I/O はどうしたってよくならないので、同期 I/O で頑張るか、libev などフレームワークの活用を考えるしかありません。
夢のような話は、夢のままでしたね。
ACCESS Advent Calendar を最後までお読みいただき、感謝申し上げます。