为什么 a+=1 是 ruby​​ 中的线程安全操作?

lon*_* ma 1 ruby multithreading thread-safety

代码片段:

a = 0
Array.new(50){
  Thread.new {
    500_000.times { a += 1 }
  }
}.each(&:join)
p "a: #{a}"
Run Code Online (Sandbox Code Playgroud)

结果:a = 25_000_000

据我了解,(MRI) Ruby 使用 GIL,所以只有一个 ruby​​ 线程可以获得 CPU,但是当发生线程切换时,会存储一些 ruby​​ 线程的数据,以便稍后恢复线程。所以,理论上,a += 1可能不是线程安全的。

但上面的结果证明我错了。Ruby 是否具有a+=1原子性?如果为真,哪些操作可以被认为是线程安全的?

Tod*_*obs 5

它既不是原子的也不是线程安全的

在您的示例中,明显的一致性主要是由于全局解释器锁,但也部分是由于您的 Ruby 引擎和您的代码序列(理论上)异步线程的方式。你得到一致的结果,因为在每个线程的每个循环简单地增加的当前值,这是不是一个块本地或线程局部变量。随着YARV虚拟机上线,在同一时间只有一个线程检查或设定的电流值一个,但我真的不能说,它是一个原子操作。这只是引擎缺乏线程间实时并发性和Ruby虚拟机底层实现的副产品。

如果您担心在 Ruby 中保留线程安全性而不依赖于恰好看起来一致的特殊行为,请考虑使用诸如concurrent-ruby 之类的线程安全库。否则,您可能会依赖 Ruby 引擎或 Ruby 版本无法保证的行为。

例如,在JRuby的代码(这三个连续的运行确实有并发线程)一般产生在每次运行时不同的结果。例如:

  1. #=> "a: 3353241"
  2. #=> "a: 3088145"
  3. #=> "a: 2642263"