佐藤先生がErlang、Scala、Javaなどの並行処理を斬る!
ここ数日の佐藤先生のエントリが熱い! Erlang、Scala、Go、Javaなどの現代の言語(Erlangは古いか)における並行処理の扱い方について、それぞれの歴史的背景や意義、得手不得手などがわかりやすく紹介されてます。80年代から並行処理やオブジェクト指向を研究されてきた佐藤先生ならではの視点ですね。ちょっと長くなりますが特に私が興味深かった部分を引用します(強調は私):
最近、興味深いのはオブジェクト指向言語のScalaやErlangが話題を集めていることでしょうか。どちらもActor Modelをベースにしているそうですが、オブジェクト指向言語の歴史でいうと、Actor Modelなどの並行処理用オブジェクト指向言語の研究が盛んになったのは1985年からの6,7年ぐらいだと思います(Actor Model自身はもっと古いですが)。そして1990年後ぐらいから議論されてきた話題のひとつは、各オブジェクトは高々のひとつの実行スレッドとするシングルスレッド実行モデルと、一つのオブジェクトでも複数同時処理を許すマルチスレッド実行モデルのどちらがいいかというもの。 さてシングルスレッド実行モデルとマルチスレッド実行モデルは何が違うかというと、前者は各オブジェクトは能動的に処理してもよい、つまり各オブジェクトに高々一つのスレッドを割り当てるが、オブジェクト内部的には逐次プログラムですから、オブジェクト内部で排他実行や同期をしなくてもいいので、オブジェクトのプログラミングは楽になります。一方、マルチスレッド実行モデルでは各オブジェクトは能動的に処理されますし、各オブジェクトにも複数のスレッドが割り当てられます。このため処理効率はあがりますが、オブジェクト内部で排他実行や同期する必要が出てきます。どちらもメリットとデメリットがあるのですが、シングルスレッド実行モデルの代表格というのが前述のActor Modelでした。
また、構文的な記述ですが、非明示的に並行処理を記述する方法もいくつか導入されました。例えばAdaなどはCSP的なガード付きコマンドという概念を導入して、スレッド実行可能な関数やサブルーチンに実行に必要な前提条件を定義しておいて、その前提条件が成立すると実行されるという方法をとりました。例えば通信データが届くと、所定のサブルーチンが呼び出されて実行するやり方。この方法ではスレッド起動そのものはシステム側に任せることになりますが、通信や制御のプログラムの場合はイベント駆動になることが多いのですが、この方法はイベント駆動的な処理では便利なので他の並行処理記述言語でも導入されました。最近でいうとGoもこれに近いプリミティブを導入しています。 ちょっと話題から外れますが、1990年前後に盛んに研究された並列Lispを含む並列関数型プログラミング言語では、関数単位にスレッドを割り当てました。これは関数の実行には副作用がすくないことを利用したわけですが、関数自体はどう呼び出されるかわからないので、並列度が上がりすぎるという問題を抱えていました。マルチコア時代は関数型言語の時代とかおっしゃる方は多いのですが、並列度を上げればいいというものでもなく、実際、スレッド数が多くなるとコンテキストスイッチのコストが大きくなります。それと歴史的に言えばプログラマーが並行実行を制御できない言語はベンチマークではよくても実システムでは向かないのですよね。だから最近話題になっている関数型言語による並列処理も同じ問題を抱えることになるでしょう。
しかし、1995年頃に登場したJavaはスレッドそのものをオブジェクトとして扱う、つまりスレッド実行をオブジェクトとして導入して、そのオブジェクトに処理を渡すと並行処理されるという方法を導入しており、そのJavaの普及とともに並行処理の記述方法の議論は下火になってしまいました。昨日の話題にあげたScalaやErlangは結局、時計の針を戻すというか、Java登場以前にもどった感じなのですよね。 脱線しますが、Javaの並行記述は中途半端でしたね。スレッドそのものをオブジェクトとしたので、Java言語自体には並行記述と独立させることに成功したのに(ここまではよかった)、同期制御(synchronized)を言語側に導入してしまったので、並行実行はオブジェクトとして定義して、一方で並行実行の同期制御は言語として定義するということになっています。インターフェースに類似した方法で、クラス定義そのものとは別に同期制御を宣言できるようにしておくべきだったかもしれません。というのはクラス定義ではあくまでも機能要件を書くべきであり、同期などの非機能要件はクラス定義に埋め込まない方がいいので。
あとElrangは交換機用言語として設計されたこともあり、実行単位の粒度の違いは気をつけた方がいいかもね。交換機の実行モデル(そんなものがあったとはもえないが)は実行単位が小さい、つまりスレッド粒度が小さいので、通常ではスレッドに割り当てなかったような小さい処理もスレッドに割り当てた方が実装しやすいし、スループットもあがるのですが、これをサーバ環境で動かした場合に実用になるかは別問題。そうそう元交換機技術者の方が Erlangはうまく使いこなせるかもしれませんね。 ただ、Erlangという意味ではないですが、一般論として交換機用の言語は弱いところもあります。交換機の世界というのはエラーハンドリングを含めてハードウェア的に多くのことが解決される世界、TCP/IPのようになんでもソフトウェア的に解決しないと世界には向かないのです。Erlang好きの人に反論されるそうなので、ちょっと補足しておきますが、いまのErlangは交換機だけではないのでしょうが、癖がある言語である以上は記述対象がErlang向きか不向きかを見抜く眼力に必要だと思います。