結論
Ractorで以下のコードを動くようにしてみました。
r = Ractor.new do
v1, v2 = Ractor.recv
puts v1
puts v2
puts v1.class
puts v2.class
end
r.send(1, 2)
r.take
# => 1
# => 2
# => Integer
# => Integer
Ractorって?
Ruby3で導入される並行・並列機能を提供するしくみです。
元々はGuildという名前で数年前から議論されてきたものです。
詳しい話は下記の動画を参照して頂ければと思います。
[JA] Ractor report / Koichi Sasada @ko1
Ractorへオブジェクトを渡す
send
メソッドを使ってRactorへとオブジェクトを渡すことができます。
r = Ractor.new do
v = Ractor.recv
puts v
puts v.class
end
r.send(1)
r.take
# => 1
# => Integer
しかし、以下のように複数のオブジェクトを渡すことはできません。
r = Ractor.new do
v1, v2 = Ractor.recv
puts v1
puts v2
puts v1.class
puts v2.class
end
r.send(1, 2)
r.take
# =>wrong number of arguments (given 2, expected 1) (ArgumentError)
ただし配列で渡す分にはOKみたいです。
r = Ractor.new do
v1, v2 = Ractor.recv
puts v1
puts v2
puts v1.class
puts v2.class
end
r.send([1, 2])
r.take
# => 1
# => 2
# => Integer
# => Integer
実装を見てみると以下のようになっています(CRubyのソースコード内のractor.rb
にて)
現在のsend
メソッドは一つのオブジェクトのみを引数に受け取ります。またムーヴして良いかどうかをキーワード引数move
で指定することもできます。
def send obj, move: false
__builtin_cexpr! %q{
ractor_send(ec, RACTOR_PTR(self), obj, move)
}
end
__builtin_cexpr!
でCの関数を呼び出し、メソッドが受け取った引数をそのままCの関数に渡しています。余談ですが、最近のCRubyでは内部実装としてRubyの変数をCの関数に渡すようなコードを書くことができるようになっています。
やったこと
Ractorのsend
メソッドを以下のように書き替えてみました。
def send obj, *arg, move: false
obj = arg.unshift obj unless arg.empty?
__builtin_cexpr! %q{
ractor_send(ec, RACTOR_PTR(self), obj, move)
}
end
まず、send
メソッドは必ず一つのオブジェクトを引数として受け取っています。その挙動を維持するためにobj, *arg, move: false
のように引数を書き換えています。
またsend(1, 2)
のように複数オブジェクトが渡された場合は*arg
に配列として引数が渡されます。
arg
が空の配列でない場合、複数オブジェクトが渡されていることになり、最終的にCの関数に渡されるobj
を第一引数と可変長引数をマージしたものへと変換しています。
あとは修正したCRubyのソースコードをビルドすればOKです。
これで以下のようにRactorに複数のオブジェクトを渡すことができます。
r = Ractor.new do
v1, v2 = Ractor.recv
puts v1
puts v2
puts v1.class
puts v2.class
end
r.send(1, 2)
r.take
# => 1
# => 2
# => Integer
# => Integer
参考
ref: Guild → Ractor
ref: https://github.com/ko1/ruby/blob/ractor/ractor.ja.md
ref: [[JA Ractor report / Koichi Sasada @ko1
追記
ちなみに、モンキーパッチでよければ以下のようにラップしたメソッドを作成すればOKです
class Ractor
def multi_send(obj, *args, move: true)
obj = args.unshift obj unless args.empty?
send(obj, move: move)
end
end
r = Ractor.new do
v1, v2 = Ractor.recv
puts v1
puts v2
puts v1.class
puts v2.class
end
r.multi_send(1, 2)
r.take
モンキーパッチだと範囲が広いので実務とかで使うならrefinements
使った方が影響が少ないのでいいかもしれない。
module RefineRactor
refine Ractor do
def multi_send(obj, *args, move: true)
obj = args.unshift obj unless args.empty?
send(obj, move: move)
end
end
end
using RefineRactor
r = Ractor.new do
v1, v2 = Ractor.recv
puts v1
puts v2
puts v1.class
puts v2.class
end
r.multi_send(1, 2)
r.take