本文简要介绍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类。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。