Ruby 3中引入了Ractor,处于试验阶段。Ractor是Ruby中对Actor模型的实现。
Actor模型
Actor模型中,一切皆Actor。Actor只能访问、更改自己内部的状态,无法访问其他Actor中的状态。这样可以避免在并发系统中,多个进程去更改同一份数据。Actor与其他Actor通过消息通信。
创建Ractor
通过Ractor.new来创建,如:
Ractor.new { puts "Hello, I'm a ractor."}
创建Ractor的时候,可以指定名字,如:
Ractor.new(name: "ractor 1") {nil}
Ractor内部是无法访问外部创建的对象的,如:
animal = "Elephant"
Ractor.new {puts "#{animal} is an mammal"}
# 执行如上代码会报错 ArgumentError (can not isolate a Proc because it accesses outer variables (animal).)
但是我们可以把Ractor外部的对象作为参数在创建Ractor的时候传递给Ractor,如:
animal = "Elephant"
Ractor.new animal do |a|
puts "#{a} is an animal"
end
# 执行上述代码,输出 "Elephant is an mammal"
Ractor中的消息机制
Ractor中有两种消息机制
Push消息机制
这种机制中,消息的发送者是知道接收者的。但是,消息的接收者不知道发送者。通过send来发送消息,receive来接收消息,如:
receiver = Ractor.new do
msg = Ractor.receive
puts "received message is #{msg}"
end
message = "Hi!"
receiver.send(message)
# 执行上述代码,输出 "received message is Hi!"
Pull消息机制
这种机制中,消息的发送者不知道接收者。但是,消息的接收者知道发送。通过yield来发送消息,take来获取消息,如:
sender = Ractor.new do
message = "Hi!"
Ractor.yield(message)
end
message = sender.take
puts "received message is #{message}"
# 执行上述代码,输出 "received message is Hi!"
如果sender Ractor没有使用yield方法,那block中最后被执行解析的对象会被返回。
对象共享
Ractor中只有不可变的对象被认为是可共享的。可共享的对象类型包括:number、boolean、module、class、ractor。string是可变的,因此不被认为是可共享的。但是,对string执行freeze方法后,是不可变的,就可以作为共享对象。
可以使用Ractor.shareable?方法判断一个对象是不是可共享,如:
Ractor.shareable? "I'm a string" # 返回false
Ractor.shareable? "I'm a string".freeze # 返回true
我们可以调用Ractor.make_shareable方法将对象变为可共享的,如:
mutable_object = "I'm not shareable"
Ractor.shareable? mutable_object # 返回false
Ractor.make_shareable mutable_object
Ractor.shareable? mutable_object # 返回true
如果将一个不可共享的对象通过Ractor作为消息发送,那么这个对象会被进行一次深度拷贝,拷贝对象会作为真正的消息通过Ractor发送。
我们可以在将不可共享的对象通过Ractor发送的时候,设置move参数为true,来避免深度拷贝,如:
receiver.send(mutable_object, move: true)
但是,这样做的一个副作用就是,这个mutable_object对象变为不可访问,也就是对它调用方法会抛出错误。