イベントはスレッドに比べて何故ダメなのか (Why Events Are A Bad Idea)

このエントリーをはてなブックマークに追加

並行処理をプログラミングする方法は大別すると、 スレッドなどを利用した同期処理的な書き方と イベントを利用した非同期処理的な書き方があります (イベントループ)。 最近 C10K問題 が有名になったことや、 Node.js が流行っていて Node.js のメリットとしてイベントモデルであるため C10Kが解決されるというのが上げられていたりして、 イベントモデルのスレッドに対する優位性が注目されることが多いように思います。

しかし個人的な経験として、ある程度以上複雑なプログラムを書く場合、 イベントモデルで非同期処理を多用してプログラムを書くとスレッドで同期処理を使ってプログラムを書いた場合に比べてプログラムの可読性・保守性が著しく悪くなり、 バグが発生しやすくなるように感じます。 しかも並行処理で起こるバグは特定の処理が特定の順序で実行された場合のみ起こるようなことが多く、 再現性が低いのでデバッグが非常に面倒なことになります。 個人的には並行処理の同時処理数が 10K にもならず、 排他制御のための処理のコストやコンテキストスイッチのコストが無視できないほどパフォーマンスが重要ならない 多くのプログラムでは並行処理はイベントモデルではなくスレッドモデルで記述すべきだと思います。

そこでイベントモデルの欠点・デメリットが体系的に整理されている Why Events Are A Bad Idea という2003年に書かれた論文を読んだので要点をまとめてみました。

論文の概要

スレッドモデルの場合、プログラムの状態 (プログラムの実行位置や処理中のデータの内容) はプログラムカウンタとスタックによって管理されます。 プログラムカウンタとスタックを使った状態を管理する部分のロジックは言語処理系が作成してくれます。 また並行している処理はカーネルによってスケジューリングされ、ある程度賢く順番に実行されます。

一方で、イベントモデルではアプリケーションレベルでプログラムの状態管理とスケジューリングは全て自前で行うことになります。 そのためイベントモデルでプログラムを書くとスレッドライブラリ・カーネル・言語処理系の実装によって引き起こされる数々の問題 (例えばスレッド数の上限や、多数のスレッドによるパフォーマンス低下、コンテキストスイッチのコスト) を回避することができます。 その代償として、イベントモデルではスレッドライブラリや言語処理系が提供する非常に便利な機能 (関数呼び出し、例外処理、スケジューリング、デバッガ、etc..) が利用できなくなり、 全て自前で実装し制御する必要があるのでプログラムが非常に複雑になってしまいます。まとめると

ということになります。 この論文では2.2で「イベントモデルの長所」として知られている点が実は(あまり大きな)長所ではないとということを、 3章で「イベントモデルの短所」を具体的に述べ、 それによっていかにプログラムが複雑で保守しづらいものになってしまうかを指摘しています。 以下に各章の要点を簡潔にまとめます。

2.1 Duality Revisited

イベントモデルとスレッドモデルは双対である。

2.2 “Problems” with Threads

Performance

Control Flow

Synchronization

State Management

Scheduling

スケジューリングがアプリケーションレベルで行えるので、 イベント方式だとアプリケーションレイヤの知識を利用してスケジューリングを最適化する余地がある。 例えば同種のイベントをまとめて処理すると code locality を上げられるなど (逆に同じデータは同じプロセッサで処理したほうが data locality が上がってよいケースもあるだろう)。

3 The Case for Threads

大体の場合 concurrent requests はそもそも独立して処理される。

Control Flow

イベントモデルだとプログラム理解する際に プログラマがプログラムを call-return のパターンに頭の中でマッチさせなくてはいけない (The programmer must mentally match these call/return pairs)。 またイベントモデルで書く場合、stateの保持を自分で書かなくてはならならず(stack ripping)、 これがイベントモデルを使う際の大きな重荷となる (実際そう思う)。 あと、control flow が形式的に縛られなくなることで本来1つしか届かないメッセージが複数呼び出される(あるいはコールバックが必要以上に呼び出される) ような問題が起こりやすい (言語やライブラリの仕様としてそういう事態を防ぐのは困難)。 あと既存のデバッグツールとの相性がよい(例えばスタックの状態の表示とか。イベントモデルで自前で状態管理してるとそういったツールからは扱いづらい)。

Exception Handling

例外発生時の処理とかややこしいから言語の機能をそのまま使える thread モデルのほうがよいよねという。 control flow の派生の話.

このエントリーをはてなブックマークに追加
Home