Ruby 1.9でのファイバー (コルーチン) の追加、および最近のErlangおよびアクターの人気など、あまり知られていない概念がRubyプログラミングの世界に入ってきている。並行性を考えたときにRubyの世界で何が使用可能であるかを大まかに理解するため、長年RubyコミュニティのメンバーであるMenTaLguY氏(source)に話を伺った。彼は、1.8.x MRIでスレッド化を改善したfastthread(source)ライブラリなど、Rubyの並行性およびスレッド化に長い間関わってきた。最近では、Rubinius(サイト・英語)にも関わっており、JRubyチームのメンバーにもなっている。

InfoQ: アクターライブラリは、Rubyにどのような影響がありますか？

MenTaLguY: 私は、実際には2つのアクターライブラリを作成 (リリース) しました。1つは、Omnibus並行性ライブラリで、もう1つはRubinius stdlibの一部となっているものです。どちらも、Erlangによって広まった並行性モデルであるアクターモデルのRuby実装です。並行性は、コードを並行して実行するという意味では、それ程実現の難しいものではありません。難しい問題が発生するのは、制御が別の複数のスレッドで、通信することや、1つのリソースを共有することが必要となった場合です。このような通信を組み立てられるように何らかの単純な形式モデルを採用しなければ、正しいコード、または必ず意味のあるコードを作成することはほぼ不可能です。表面的には「動いている」ように見えても、実際には正常に機能しない場合もあります。

関連スポンサーコンテンツ

「アクター」は、このような場合のためのモデルです。アクターは、メールボックスとスレッドで構成されます。アクタースレッドは、自立的に、特定の種類のメッセージがメールボックスに現れるのを待機しています。メッセージが発生すると、その種類に応じて、他のアクターにメッセージを送信するなどの動作 (act) をします。このように、スレッドは、自立的で明示的なメッセージ交換によって動くので、比較的簡単に想像できる方法で通信できます。

InfoQ: アクターは、Rubyのスレッド化システム、またはRubyの新しいファイバーやコルーチンにどのように関係しますか？

MenTaLguY: 私の作成したアクターライブラリは、単純に、各Rubyスレッドに1つのメールボックスを関連付け、それぞれにアクターを作成します。しかし、これがRubyでアクターを使用する際の唯一の方法というわけではありません。ファイバーは、単純に、単一のスレッド内で協調的にスケジュールされたタスクですが、これの上にアクターを置くこともできます。実際、Tony Arcieri氏は彼のRevactorライブラリ(サイト・英語)でこのようの方法を使用しています。ファイバーはフルスレッドよりも軽量であり、また割り込みを考慮する必要もないため、彼の方法にはいくつかの利点があります。ただし、フルスレッドが必要になる場合もあります (Ruby stdlibはフルスレッドを必要とすることがあります)。

Tony氏と私は、設計について活発かつ生産的な議論を重ねています。計画としては、アクター実装はできる限り互換性のあるものにし、他のアクター実装も連携するために使用できる、単純なオブジェクトプロトコルを提供しようとしています。外側からは、アクターは同じものをダックタイピングすることができます。つまり、行うべきことは、どのような場合も、メッセージをメールボックスに送信することだけです。ほとんどの部分において、そのメッセージを処理しているのがスレッドであるのかファイバーであるのか、あるいは別のRuby VMで実行されているものであるのかは問題になりません。原則としてアクターのダックは、何らかの場所でErlangのプロセスに依存することも可能です (例えば、Scott FleckensteinのErlectricityを経由するなど)。

InfoQ: 最近、このアクターに関するコミット(source)のように、Rubiniusリポジトリのコミットをいくつか見ましたが、アクターは、Rubiniusで使用されますか？

MenTaLguY: 実装の一部としては使用されません (これは、私がアクターをコアからstdlibに移動した理由の1つです)。私は、アクターがそのようなレベルで必要とされるとは考えていません。

InfoQ: アクターまたはそのメールボックスの実装は、最近Evan氏がRubiniusに追加したメッセージ送信マルチVM IPC(source)で使用されますか (その可能性はありますか)？

MenTaLguY: アクターを、マルチVM IPCメカニズムを実装するために使用することはありません。しかし、別々のVM内のアクターが相互に通信することを可能にするために、背後でマルチMVM IPCを使用することはあると思います。

InfoQ: Rubiniusのスレッド化は、現在どのような状態ですか？何が使用されますか？ユーザスペーススレッド、カーネルスレッド、あるいはm:nでの両方の組み合わせですか？

MenTaLguY: VM内では、複数のユーザスペーススレッドを使用しますが、それぞれのVMは別々のカーネルスレッドで実行されます。現在のところ、複数のCPUがありそれをすべて使用する場合は、それぞれに1つまたは2つのVMを作成します。Evan氏は、最終的にはVMの中でm:nのスレッド化を行うことを望んでいますが、Rubyには、そのために乗り越えなければならない技術的課題が数多くあります。Ruby 1.9でも、ネイティブスレッドは制限しているため、事実上はユーザスペーススレッドになっています。

最初からネイティブスレッド化ランタイム (XRuby、JRuby、IronRuby) に基づいて構築されたRuby実装であれば、ネイティブスレッドを完全にサポートできる可能性があります。結局、ネイティブスレッドは、マルチVMが十分に軽量化できるか、CPU間の通信の方が高価になる (このころまでには、NUMAがより普及している) のであれば、あまり重要ではなくなります。

しかし、ネイティブスレッドを避けられない状況が1つあります。それは、IO APIが、適切に設計されていないために非同期動作をサポートしない場合です。 このような場合、複数のネイティブスレッドは必要としますが、マルチコアを活用できるわけではなく、何もすることができません。場合によっては、犠牲とするスレッドに、ブロックしている呼び出しの終了を待機させて、他のコードを問題なく処理する以外に方法がないこともあります。

今後、このようなAPIが減っていくことを願っています。Tony氏のRevactorライブラリは、このような問題にとって希望の光となります。アクターにIOを行わせることで、コードの実行が、ブロックしている呼び出しを無駄に待機したり、制御の反転や巨大な明示的ステートマシンを用いたりするのではなく、IOイベントによって進むようにします。現在のところ、これはMRI 1.9に結合されていますが、私達は、他のRuby実装でも、これを移植するか、または同様のものを実現することが可能です。

InfoQ: Rubiniusには、スレッド、アクター、マルチVM、メッセージ送信IPCなど、並行性に関して豊富なアイディアとツールが含まれているように見えます。

MenTaLguY: 並行性は、現在、特に重要な関心事となっています。Rubiniusはこうした状況を反映しているのだと思います。

InfoQ: これらのツールの1つとして私が気付いたのは、チャネルです。これは、Rubiniusの中でどのような役割を果たしますか？(例えば、高速デバッガは、デバッガスレッドに通知を行うためにチャネルを使用していると理解しています。)

MenTaLguY: チャネルは、Rubiniusにおける基本的な通信の基礎となる要素です。他のすべてのものは、チャネルの上に実装されています。基本的な並行性モデルは、大まかに言えば、複製なしの非同期Pi calculus[GLOVA1]と、非決定的選択のようなよくあるいくつかの拡張を合わせたものです (これは、潜在的に、チャネル操作が中央でアービトレーションされることを必要とします)。私がPi calculusチャネルをお勧めするのは、それが単純であるからです。単純性は、一般的に、パフォーマンスと保守性に反映されてきます。

現在のところ、Pi calculusは、「ローカル」(VM内) のものに直接的に使用する場合に適切です。Pi calculusではチャネルの両端が可動であるので、分散されるものの実装にはあまり適していません。書き込みが非同期である場合、開始だけして後は何もしないということが可能です。しかし、チャネルからの読み取りは同期操作です。1つのチャネルが複数の読み取り者に渡された場合、それらの読み取り者は、それぞれの読み取りを行うためにどこかに集合する必要があります。それぞれが特に離れている場合には、適切なものとは言えません!

これは、私が、大規模なものを対象としたときにアクターに関心を持った理由の1つです。アクターメールボックスは、非同期calculusのチャネルに似ています。違いは、(非同期) 書き込みの端だけが独立して可動であるという点です。読み取りの端は、特定のローカルエージェントに固く固定されています。特別な「長距離」連携は必要ありません。

InfoQ: Ruby 1.9では、ファイバーとコルーチンが追加されました。これらは、Rubiniusにおいてどのように実装されますか？

MenTaLguY: ファイバーは、Rubiniusタスクによってあまり問題なく実装できると考えています。ファイバーとタスクはとても似ています。

InfoQ: ファイバーやコルーチンについて、何か見通しや意見はありますか？これらを使用するとすれば、それは何のためですか？

MenTaLguY: これらは、明示的ステートマシンの代替に適していると思います。特に、コルーチンは、ライブラリコードを使用するときの自由度を大きくしてくれると思います。しかし、ステートマシンが十分に小さい場合、またはそれを生成するのにRagelのようなものを使用するのが適切である場合は、まだステートマシンの方が優れています。

InfoQ: 現在JRubyのコミット権を持っているというのは本当ですか？JRubyの何に興味を持っていますか？また、何に取り組んでいますか？

MenTaLguY: 本当です。私の関心の対象は、主に並行性です。Rubyとネイティブスレッドの連携には、いくつかの面白い課題があります。私は、並行性に関するバグを修正したり、一般的にどのような並行性の保証を提供すべきかということを検討したりしています。また最終的には、できれば移植可能な方法で、Java並行性機能をRubyに適切に公開しようと思っています (これは、部分的にOmnibus並行性ライブラリのミッションになります)。

InfoQ: 他にはどのようなプロジェクトに関わっていますか？

MenTaLguY:たまに、Shoesにパッチを提供したりしています。これに加えて、多くのライブラリに影で協力しています。これらの多くは、準備ができ次第、発表およびリリースされる予定です。まだ正式には発表されていませんが、私が最近リリースしたものを1つここで紹介します。それは、「case」です。これは、配列、構造体、または任意の述語用のパターンマッチングを、Rubyのcase-match演算子を使用して提供します。

require 'rubygems'

require 'case'



Foo = Case::Struct.new :a, :b





def example(arg)

case arg

when Foo[:blarg, Object] # matches any Foo with .a == :blarg

# ...

when Foo[10, 20] # matches only a Foo with .a == 10 and .b == 20

# ...

when Foo # matches any Foo

# ...

when Case::Any[String, Array] # matches either a String or Array

# ...

# matches a three-element array with initial elements 1, 2:

when Case[1, 2, Object]

# ...

# matches any Integer > 10:

when Case::All[Integer, Case.guard { |n| n > 10 }]

# ...

end

end

Tony氏も私も、待機するメッセージの種類を選択するのに、アクターライブラリでcase-match演算子 (===) を使用しています。この演算子は、このような場合に非常に便利です。

MenTaLguYの話をさらに調べたい場合は、http://moonbase.rydia.net/ (英語)を参照するか、彼が関わっているプロジェクトを見て欲しい。アクターの詳細については、Revactorの開発者であるTony Arcieri氏への最近のインタビュー(source)を読むことをお勧めする。Revactor(サイト・英語)は、高パフォーマンスネットワークアプリケーション向けのアプリケーションプラットフォームである。これは、Ruby 1.9を対象とし、ファイバーや並行性などの機能を使用している。Rubiniusの詳細については、InfoQのRubiniusに関する記事(記事リスト)をお勧めする。

原文はこちらです：http://www.infoq.com/articles/actors-rubinius-interview

