本文簡要介紹ruby語言中 Ractor類
的用法。
Ractor
是 Ruby 的 Actor-model 抽象,提供線程安全的並行執行。
Ractor.new
可以創建一個新的 Ractor
,它將並行運行。
# The simplest ractor
r = Ractor.new {puts "I am in Ractor!"}
r.take # wait for it to finish
# here "I am in Ractor!" would be printed
Ractors 不共享通常的對象,因此data-race、race-conditions 等相同類型的線程安全問題在multi-ractor 編程中不可用。
為了實現這一點,ractor 嚴格限製了不同 ractor 之間的對象共享。例如,與線程不同,ractor 不能訪問彼此的對象,也不能通過外部範圍的變量訪問任何對象。
a = 1
r = Ractor.new {puts "I am in Ractor! a=#{a}"}
# fails immediately with
# ArgumentError (can not isolate a Proc because it accesses outer variables (a).)
在 CRuby(默認實現)上,全局虛擬機鎖 (GVL) 由每個 ractor 持有,因此 ractor 並行執行而不會相互鎖定。
與其訪問共享狀態,不如通過將對象作為消息發送和接收來將對象傳入和傳出 ractor。
a = 1
r = Ractor.new do
a_in_ractor = receive # receive blocks till somebody will pass message
puts "I am in Ractor! a=#{a_in_ractor}"
end
r.send(a) # pass it
r.take
# here "I am in Ractor! a=1" would be printed
有兩對發送/接收消息的方法:
-
Ractor#send
和Ractor.receive
用於當sender
知道接收者(推送)時; -
Ractor.yield
和Ractor#take
用於當receiver
知道發送者時(拉取);
除此之外, Ractor.new
的參數將被傳遞給 block 並在那裏可用,就像由 Ractor.receive
接收一樣,最後一個塊值將被發送到 ractor 之外,就像由 Ractor.yield
發送一樣。
經典ping-pong的小演示:
server = Ractor.new do
puts "Server starts: #{self.inspect}"
puts "Server sends: ping"
Ractor.yield 'ping' # The server doesn't know the receiver and sends to whoever interested
received = Ractor.receive # The server doesn't know the sender and receives from whoever sent
puts "Server received: #{received}"
end
client = Ractor.new(server) do |srv| # The server is sent inside client, and available as srv
puts "Client starts: #{self.inspect}"
received = srv.take # The Client takes a message specifically from the server
puts "Client received from " \
"#{srv.inspect}: #{received}"
puts "Client sends to " \
"#{srv.inspect}: pong"
srv.send 'pong' # The client sends a message specifically to the server
end
[client, server].each(&:take) # Wait till they both finish
這將輸出:
Server starts: #<Ractor:#2 test.rb:1 running> Server sends: ping Client starts: #<Ractor:#3 test.rb:8 running> Client received from #<Ractor:#2 rac.rb:1 blocking>: ping Client sends to #<Ractor:#2 rac.rb:1 blocking>: pong Server received: pong
據說 Ractor
通過 incoming port
接收消息,並將它們發送到 outgoing port
。可以分別使用 Ractor#close_incoming
和 Ractor#close_outgoing
禁用任何一個。如果一個 ractor 終止,它的端口將自動關閉。
可共享和不可共享的對象
當對象被發送到和從 ractor 發送時,了解對象是可共享的還是不可共享的很重要。大多數對象是不可共享的對象。
可共享對象本質上是可以被多個線程使用而不影響線程安全的對象;例如不可變的。 Ractor.shareable?
允許檢查這一點,如果不是, Ractor.make_shareable
會嘗試使對象可共享。
Ractor.shareable?(1) #=> true -- numbers and other immutable basic values are
Ractor.shareable?('foo') #=> false, unless the string is frozen due to # freeze_string_literals: true
Ractor.shareable?('foo'.freeze) #=> true
ary = ['hello', 'world']
ary.frozen? #=> false
ary[0].frozen? #=> false
Ractor.make_shareable(ary)
ary.frozen? #=> true
ary[0].frozen? #=> true
ary[1].frozen? #=> true
當一個可共享的對象被發送(通過 send
或 Ractor.yield
)時,不會發生額外的處理,它隻是變得可以被兩個 ractor 使用。發送不可共享對象時,它可以是 copied
或 moved
。第一個是默認設置,它通過深度克隆其結構的不可共享部分來製作對象的完整副本。
data = ['foo', 'bar'.freeze]
r = Ractor.new do
data2 = Ractor.receive
puts "In ractor: #{data2.object_id}, #{data2[0].object_id}, #{data2[1].object_id}"
end
r.send(data)
r.take
puts "Outside : #{data.object_id}, #{data[0].object_id}, #{data[1].object_id}"
這將輸出:
In ractor: 340, 360, 320 Outside : 380, 400, 320
(注意,數組和數組內的非凍結字符串的對象id在ractor內部都發生了變化,表明它是不同的對象。但是第二個數組的元素,即可共享的凍結字符串,具有相同的object_id。)
對象的深度克隆可能很慢,有時甚至是不可能的。或者,move: true
可用於發送。這會將 move
對象發送到接收 ractor,使發送 ractor 無法訪問它。
data = ['foo', 'bar']
r = Ractor.new do
data_in_ractor = Ractor.receive
puts "In ractor: #{data_in_ractor.object_id}, #{data_in_ractor[0].object_id}"
end
r.send(data, move: true)
r.take
puts "Outside: moved? #{Ractor::MovedObject === data}"
puts "Outside: #{data.inspect}"
這將輸出:
In ractor: 100, 120 Outside: moved? true test.rb:9:in `method_missing': can not send any methods to a moved object (Ractor::MovedError)
請注意,即使是 inspect
(以及更基本的方法,如 __id__
)也無法在移動的對象上訪問。
除了凍結的對象,還有可共享的對象。 Class
和 Module
對象是可共享的,因此類/模塊定義在 ractor 之間共享。 Ractor
對象也是可共享對象。可共享可變對象的所有操作都是線程安全的,因此將保留線程安全屬性。我們不能在 Ruby 中定義可變的可共享對象,但是 C 擴展可以引入它們。
禁止從除 main 之外的 ractor 訪問可變可共享對象(尤其是模塊和類)的實例變量:
class C
class << self
attr_accessor :tricky
end
end
C.tricky = 'test'
r = Ractor.new(C) do |cls|
puts "I see #{cls}"
puts "I can't see #{cls.tricky}"
end
r.take
# I see C
# can not access instance variables of classes/modules from non-main Ractors (RuntimeError)
如果常量是可共享的,則 Ractor 可以訪問常量。主 Ractor
是唯一可以訪問不可共享常量的。
GOOD = 'good'.freeze
BAD = 'bad'
r = Ractor.new do
puts "GOOD=#{GOOD}"
puts "BAD=#{BAD}"
end
r.take
# GOOD=good
# can not access non-shareable objects in constant Object::BAD by non-main Ractor. (NameError)
# Consider the same C class from above
r = Ractor.new do
puts "I see #{C}"
puts "I can't see #{C.tricky}"
end
r.take
# I see C
# can not access instance variables of classes/modules from non-main Ractors (RuntimeError)
另請參見Comments syntax 說明中的# shareable_constant_value
pragma 說明。
Ractors 與線程
每個 ractor 創建自己的線程。可以從 ractor 內部創建新線程(並且在 CRuby 上,與該 ractor 的其他線程共享 GVL)。
r = Ractor.new do
a = 1
Thread.new {puts "Thread in ractor: a=#{a}"}.join
end
r.take
# Here "Thread in ractor: a=1" will be printed
代碼示例注意事項
在下麵的示例中,有時我們使用以下方法等待當前未阻塞的 ractor 完成(或處理直到下一個阻塞)方法。
def wait
sleep(0.1)
end
它**僅用於演示目的**,不應在實際代碼中使用。大多數時候,隻使用 take
來等待ractor 完成。
參考
有關詳細信息,請參閱Ractor design doc。
相關用法
- Ruby Ractor.receive_if用法及代碼示例
- Ruby Ractor.close_incoming用法及代碼示例
- Ruby Ractor.count用法及代碼示例
- Ruby Ractor.shareable?用法及代碼示例
- Ruby Ractor.current用法及代碼示例
- Ruby Ractor.new用法及代碼示例
- Ruby Ractor.receive用法及代碼示例
- Ruby Ractor.close_outgoing用法及代碼示例
- Ruby Ractor.yield用法及代碼示例
- Ruby Ractor.send用法及代碼示例
- Ruby Ractor.take用法及代碼示例
- Ruby Ractor.make_shareable用法及代碼示例
- Ruby Ractor.select用法及代碼示例
- Ruby Racc模塊用法及代碼示例
- Ruby Range.end用法及代碼示例
- Ruby Range new()用法及代碼示例
- Ruby Rational.inspect用法及代碼示例
- Ruby Random.bytes用法及代碼示例
- Ruby Random hex()用法及代碼示例
- Ruby Range.size用法及代碼示例
- Ruby Rational.rational <=>用法及代碼示例
- Ruby Rational to_i()用法及代碼示例
- Ruby Rational.rat ** numeric用法及代碼示例
- Ruby Random random_number()用法及代碼示例
- Ruby Range last()用法及代碼示例
注:本文由純淨天空篩選整理自ruby-lang.org大神的英文原創作品 Ractor類。非經特殊聲明,原始代碼版權歸原作者所有,本譯文未經允許或授權,請勿轉載或複製。