[ruby] Ruby3中的Ractor

361 阅读2分钟

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中只有不可变的对象被认为是可共享的。可共享的对象类型包括:numberbooleanmoduleclassractor。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对象变为不可访问,也就是对它调用方法会抛出错误。

References

Introduction to Ractor in Ruby 3

Ractor spec in Ruby repo