【Ruby 3.0 Advent Calendar 2020】Ractor の共有可能オブジェクトについて【24日目】
Ruby 3.0 Advent Calendar 2020 24日目の記事になります。
今回は Ruby 3.0 で実験的に入る Ractor の共有可能オブジェクトについて簡単に説明してみます。
NOTE: ここに書いてある内容は今後変更されるかもしれないので注意してください!!
共有可能オブジェクトとは
通常 Ractor 間でオブジェクトのやり取りを場合は #send
+ Ractor.receive
や Ractor.yield
+ #take
をつかったりします。
ractor = Ractor.new { # send の値を受け取る obj = Ractor.receive p obj # => [1, 2, 3] # take の戻り値として返す Ractor.yield obj.map { _1 + _1 } } # send で渡した値を Ractor.receive で受け取る ractor.send [1, 2, 3] # Ractor.yield の引数を受け取る p ractor.take
こんな感じで任意のオブジェクトを渡したり受け取ったりします。
この時に普通にオブジェクトを渡した場合はそのコピーが Ractor へと渡されます。
ractor = Ractor.new { obj = Ractor.receive p obj.__id__ # => 80 } obj = [1, 2, 3] p obj.__id__ # => 60 # obj のコピーを Ractor へと渡す ractor.send obj ractor.take
この時に obj
を freeze
するとコピーされずに Ractor へと渡されます。
ractor = Ractor.new { obj = Ractor.receive p obj.__id__ # => 60 } obj = [1, 2, 3] obj.freeze p obj.__id__ # => 60 # obj はコピーされずに Ractor へと渡される ractor.send obj ractor.take
このように『コピーせずに渡されるオブジェクト』の事を『共有可能オブジェクト』と呼びます。
『共有可能オブジェクト』は Ractor.shareable?
で判定する事ができます。
共有可能オブジェクトの条件は以下になります。
- 不変なオブジェクト
freeze
されているオブジェクト- 整数やシンボルなどはデフォルトで
freeze
されているのでこれに該当する - オブジェクトが参照する要素が全て不変である必要がある
- クラス・モジュール
- その他、特別なオブジェクト
Ractor
オブジェクトなどなど
# 共有可能オブジェクト p Ractor.shareable? 1 # => true p Ractor.shareable? :hoge # => true p Ractor.shareable? nil # => true p Ractor.shareable? false # => true p Ractor.shareable? [1, 2].freeze # => true p Ractor.shareable? "hoge".freeze # => true p Ractor.shareable? Array # => true class X; end p Ractor.shareable? X # => true p Ractor.shareable? Ractor.new {} # => true puts "-------------" # 共有可能オブジェクトではない p Ractor.shareable? [1, 2] # => false p Ractor.shareable? "hoge" # => false p Ractor.shareable? [1, 2, "hoge"] # => false p Ractor.shareable? [1, 2, "hoge"].freeze # => false
共有可能オブジェクト化する
共有可能オブジェクト化する手段はいくつかあるので紹介します。
.freeze
する
一番シンプルなのが .freeze
する方法です。
obj = "hoge" # 共有可能オブジェクトではない p Ractor.shareable? obj # => false # freeze すると共有可能オブジェクトとして扱われる obj.freeze p Ractor.shareable? obj # => true
ただし、配列やハッシュなどは保持している要素全てが .freeze
されている必要があります。
obj = [1, 2, "hoge"] # 共有可能オブジェクトではない p Ractor.shareable? obj # => false # obj だけ freeze してもダメ obj.freeze p Ractor.shareable? obj # => false # 要素も全て freeze されている必要がある obj[2].freeze p Ractor.shareable? obj # => true
Ractor.make_shareable
先程のネストした配列のようなオブジェクトを一発で共有可能オブジェクトにしたい場合には Ractor.make_shareable
が利用できます。
obj = [1, 2, "hoge"] # 共有可能オブジェクトではない p Ractor.shareable? obj # => false # obj の中身を全て freeze する Ractor.make_shareable obj p Ractor.shareable? obj # => true p obj.frozen? # => true p obj[2].frozen? # => true
基本的に任意のオブジェクトを共有可能オブジェクトにしたい場合は Ractor.make_shareable
を利用するといいと思います。
マジックコメントで定数を共有可能オブジェクト化する
専用のマジックコメントを記述しておくことで定数をデフォルトで共有可能オブジェクトとして定義する事ができます。
experimental_everything
: マジックコメント移行の定数定義を共有可能オブジェクトにするexperimental_copy
: 値をコピーを共有可能オブジェクトにして定数を定義するnone
:shareable_constant_value
を無効にするliteral
: 定数定義がリテラルだった場合のみ共有可能オブジェクトにすり
experimental_everything
# マジックコメント以下の定数が共有可能オブジェクトとして定義される # shareable_constant_value: experimental_everything # デフォルトで定数が共有可能オブジェクトになる A = [1, 2, 3] p Ractor.shareable? A # => true # この場合は obj も共有可能オブジェクトになる obj = [1, 2, 3] B = obj p Ractor.shareable? B # => true p Ractor.shareable? obj # => true # id も同じ p obj.__id__ # => 60 p B.__id__ # => 60
experimental_copy
# マジックコメント以下の定数が共有可能オブジェクトとして定義される # 値をコピーしてから定数を定義する # shareable_constant_value: experimental_copy # デフォルトで定数が共有可能オブジェクトになる A = [1, 2, 3] p Ractor.shareable? A # => true # experimental_copy の場合は obj は共有可能オブジェクトにはならない obj = [1, 2, 3] B = obj p Ractor.shareable? B # => true p Ractor.shareable? obj # => false # id も異なる p obj.__id__ # => 60 p B.__id__ # => 80
none
# shareable_constant_value: experimental_everything A = [1, 2, 3] p Ractor.shareable? A # => true # shareable_constant_value の設定を無効にする # shareable_constant_value: none B = [1, 2, 3] p Ractor.shareable? B # => false
literal
# shareable_constant_value: literal # これは OK A = [1, 2, 3] p Ractor.shareable? A # => true # 式を渡した場合にエラーになる # error: `ensure_shareable': cannot assign unshareable object to B (Ractor::IsolationError) B = [1, 2, 3] + [4, 5, 6]
まとめ
と、言う感じで現時点でわたしが把握している情報をまとめてみました。
冒頭にも書きましたが Ractor はまだ開発中なので今後ここに書かれている挙動は変わるかもしれません。
Ractor を実際に使用する場合はオフィシャルなドキュメント等も合わせて参照してください。