みなさん、こんにちは!エンジニアの岩崎です。9/8~9/10の三日間にわたり京都で開催されたRubyKaigi 2016のレポートを前後半に分けてお送りします。
一日目
RubyKaigi 2016のオープニングはRubyの父、Matzことまつもとゆきひろ氏の基調講演で始まりました。内容はRuby3の型について。プログラミング言語にはJavaやGoのように型のある静的型付け言語と、PerlやRubyのような動的型付け言語があります。型は便利だが入れたくない!というMatzが提案したのは、Soft Typingという振る舞いによる静的型推論によって、ダックタイピングを可能にする技術でした。2020年の東京オリンピックまでにはリリースしたいとのこと。楽しみですね。
初日は素晴らしいトークが多く、取り上げるテーマについて非常に悩んだのですが、私が担当する前半のレポートでは笹田氏の「A proposal of new concurrency model for Ruby 3」を取り上げたいと思います。

A proposal of new concurrency model for Ruby 3
スピーカーである笹田耕一氏(@ko1)は言わずと知れたRubyの主要コミッタであり、YARVの開発者でもあります。Ruby3に向けてMatz以上に情熱を燃やしている人物といえるかもしれません。そんな笹田氏が提案したのが「Guild」という新しい並行モデルです。
CとRubyの違い
CとRubyではプログラミングに対するアプローチが全く異なります。
C language
- ポインタを使って文字列操作を行う
- GC(ガーベッジコレクション)のないメモリ管理
Ruby
- ポインタをString Classでラップ
- GCを用いたメモリの再利用
Cのほうがよりローレベルの操作であり、高速な分エラーの温床になりやすいといえます。それに対してRubyは「安全」で「簡単」なインターフェースを提供する言語であるといえるでしょう。それはRubyが「Happy Programming」をモットーとしている言語だからです。
並行プログラミングは難しい!
並行プログラミングに起因する問題として以下が挙げられます。
- 読み書きの競合によるデータレース、レースコンディション
- 同期のミスによるデッドロック、ライブロック
- バグが起こった時の、非決定的な性質による再現の困難さ
以下は銀行口座の操作を表現したプログラミングモデルですが、複数のスレッドで実行するとデータに不整合が生じてしまいます。
def transfer1 (amount, account_from, account_to)
if (account_from.balance < amount) return NOPE
account_to.balance += amount
account_from.balance -= amount
return YEP
end
この状態を「data race」と呼びます。ではThread.exclusive を使って、このブロックではただ一つのスレッドしか動作しないようにしておくのはどうでしょうか。
def transfer2 (amount, account_from, account_to)
if (account_from.balance < amount) return NOPE
Thread.exclusive{ account_to.balance += amount }
Thread.exclusive{ account_from.balance -= amount }
return YEP
end
残念ながらこれでも問題は解決しません。account_from.balance が amount よりも大きい、という invariant (不変条件)を壊してしまう結果race conditionが起きてしまうからです。このケースでは以下のようにメソッド全体を同期することが必要になります。
def transfer3 (amount, account_from, account_to)
Thread.exclusive{
if (account_from.balance < amount) return NOPE
account_to.balance += amount
account_from.balance -= amount
return YEP
}
end
さらにやっかいなことにCRubyやJRubyといった実装の違いにより動作が異なる場合もあります。以下のようなプログラムはCRuby(MRI)では[1,2,3]か[1,2,3,4,5,6]が表示されますが、JRubyでは例外が発生してしまいます。CRubyではGVL(Giant VM Lock)という機構によってスレッドの並列実行が制御されていますが、JRubyではconcatメソッドがスレッドセーフではないため例外が発生してしまうのです。
ary = [1, 2, 3]
t1 = Thread.new{
ary.concat [4, 5, 6]
}
t2 = Thread.new{
p ary
}.join
他の言語の例
データ共有に関して他の言語の例を見てみましょう。
- Shell script with pipes, Racket (Place)
- Erlang/Elixir
- Clojure
シェルスクリプトでは可変データをコピーすることによって、プロセス間のデータ共有を行います。ErlangやElixirではそもそも可変オブジェクトを許容しません。Clojureは基本的に可変オブジェクトを許容せず、共有用に特別なデータ構造を持ちます。
コピーはロックを必要としない反面遅いという欠点があります。Rubyでは多くの書き込み処理が実行されるため可変オブジェクトを許容しないアプローチは現実的ではありません。共有のための特別なデータ構造は有意義ですが、実装が困難であるという問題があります。これらを踏まえRuby3へのアプローチを考える必要があります。
新モデル「Guild」の提案
Ruby3の新しい並行処理機構として笹田氏は以下の条件を提案します。
- これまでの Ruby と、ほぼ互換性がある
- ロックなどの排他制御について、殆ど考えなくてもよい
- コピーで共有してもいいけど、コピーは速いほうがよい
- 共有できるデータはなるべく共有したまま使える方がよい
- 本当に性能が必要なら、難しい方法を用いて、使えば同時に読み書きできる(Clojure的)
以上を満たすモデルが「Guild」なのです。
Guildとは効率的に並行処理を行うための処理機構です。Rubyインタプリタを開始すると自動的にGuildが生成され、その中でメインスレッドが実行されます。Guild 内のスレッドはこれまで通り同時に実行されますが、Guild が持つロックによって、並列には実行しません。Guildは複数生成することも可能です。そして、読み書きを行なう可変オブジェクトは特定の Guild にのみ属します。 複数の Guild が、同時にあるオブジェクトを読み書きすることは出来ません。
以下がGuildを用いた実装例になります。
g_bank = Guild.new(script: %q{
while account_from, account_to, amount, ch = Guild.default_cahnnel.receive
if (Bank[account_from].balance < amount)
ch.transfer :NOPE
else
Bank[account_to].balance += amount
Bank[account_from].balance -= amount
ch.transfer :YEP
end
end
})
スレッドとの違い
Guildとスレッドとの違いとして、Guildが可変オブジェクトの共有を制限している点が挙げられます。Guildの場合、オブジェクトに触ることができるのは常に一つのGuildのみとなっており、誤ってロックし忘れデータレースやレースコンディションを発生させてしまう心配がありません。GuildはこれまでVM単位でGVLがかけていたロックをより小さいユニットに落とし込むものであり、スレッドより大きく、forkより小さい実行単位といえるでしょう。
Guildの仕組み
Guild同士の通信はGuild::Channelを使って行います。通信方法は以下の2つです。
Copyはオブジェクトをdeep copyして送ります。Transfer membership(移籍)は新しい概念です。全部コピーするのは難しいので、参照を送りたいですが、ただ参照を送るだけでは、 可変オブジェクトを同時に複数の Guild が共有してしまうことになり、ロックなどについて考えなければならなくなります。 そもそも、可変オブジェクトが特定の Guild に属する、という前提が崩れてしまいます。
Transfer membership(移籍)は可変オブジェクトの移動時に元のオブジェクトを脱退(leave)させ、移動先のGuildに参加(join)します。そして脱退した可変オブジェクトへの参照を用いても移動元のGuildからはアクセスできないようにします。これにより、可変オブジェクトが特定のGuildにのみ属するという状況を担保することができます。
Guildの仕組み
- Copy
- ロックなどの排他制御について、殆ど考えなくてもよい
- ransfer membership or Move in short
Guildは「Happy Programming」というRubyの思想を受け、可能な限り快適に、そして安全に並行処理を実行する技術です。Transfer membership(移籍)という新しい概念もあり、非常におもしろい試みであるといえます。Ruby3が楽しみですね!
[参考資料]
http://www.atdot.net/~ko1/activities/2016_rubykaigi.pdf
http://www.atdot.net/~ko1/diary/201609.html#d6