为什么是a+=1在ruby中是线程安全的操作吗?
代码段:为什么是a+=1在ruby中是线程安全的操作吗?,ruby,multithreading,thread-safety,Ruby,Multithreading,Thread Safety,代码段: a = 0 Array.new(50){ Thread.new { 500_000.times { a += 1 } } }.each(&:join) p "a: #{a}" 结果:a=25_000_000 据我了解,(MRI)Ruby使用GIL,所以只有一个Ruby线程可以获得CPU,但是当线程切换发生时,Ruby线程的一些数据将被存储起来,以便稍后恢复线程。因此,理论上,a+=1可能不是线程安全的 但是上面的结果证明我错了。Ruby是否使a+=1原子化?如
a = 0
Array.new(50){
Thread.new {
500_000.times { a += 1 }
}
}.each(&:join)
p "a: #{a}"
结果:a=25_000_000
据我了解,(MRI)Ruby使用GIL,所以只有一个Ruby线程可以获得CPU,但是当线程切换发生时,Ruby线程的一些数据将被存储起来,以便稍后恢复线程。因此,理论上,a+=1
可能不是线程安全的
但是上面的结果证明我错了。Ruby是否使a+=1
原子化?如果为true,哪些操作可以被认为是线程安全的?它既不是原子操作,也不是线程安全操作
在您的示例中,明显的一致性主要是由于全局解释器锁,但也部分是由于Ruby引擎和代码序列(理论上)异步线程的方式。您得到的结果是一致的,因为每个线程中的每个循环只是增加a的当前值,而a不是块局部变量或线程局部变量。对于YARV虚拟机上的线程,一次只有一个线程在检查或设置a的当前值,但我不会说这是一个原子操作。这只是引擎在线程之间缺乏实时并发性以及Ruby虚拟机的底层实现的副产品
如果您担心在Ruby中保存线程安全,而不依赖于恰好看起来一致的特殊行为,请考虑使用类似线程安全的库。否则,您可能依赖于在Ruby引擎或Ruby版本中无法保证的行为
例如,在JRuby中连续运行三次代码(确实有并发线程)通常会在每次运行时产生不同的结果。例如:#=>“a:3353241”
#=>“a:3088145”
#=>“a:2642263”
如果您担心在Ruby中保存线程安全,而不依赖于恰好看起来一致的特殊行为,请考虑使用类似线程安全的库。否则,您可能依赖于在Ruby引擎或Ruby版本中无法保证的行为
例如,在JRuby中连续运行三次代码(确实有并发线程)通常会在每次运行时产生不同的结果。例如:#=>“a:3353241”
#=>“a:3088145”
#=>“a:2642263”
Ruby没有一个定义良好的内存模型,因此在某种哲学意义上,这个问题是非理性的,因为没有内存模型,“线程安全”一词甚至没有定义。例如,甚至不记录 人们在Ruby中编写并发代码时没有定义好的内存模型,这种方式本质上是“猜测和测试”。您猜测这些实现将做什么,然后在尽可能多的平台上测试尽可能多的实现版本,在尽可能多的CPU体系结构和尽可能多的不同系统大小上测试尽可能多的操作系统 正如您在中所看到的,即使只是测试另一个实现,也已经表明您的结论是错误的。(专业提示:永远不要基于1的样本大小进行概括!)
另一种选择是使用已经完成上述工作的库,如Todd回答中提到的库。他们做我上面提到的所有测试。他们还和各种实现的维护人员密切合作。例如,TruffleRuby的首席开发人员Chris Seaton也是并发ruby的维护者之一,JRuby的首席开发人员Charlie Nutter也是贡献者之一。ruby没有一个定义良好的内存模型,因此从某种哲学意义上讲,这个问题是非理性的,因为没有内存模型,术语“线程安全”甚至没有定义。例如,甚至没有记录 人们在Ruby中编写并发代码时没有定义好的内存模型,这种方式本质上是“猜测和测试”“。您猜猜这些实现会做什么,然后在尽可能多的平台上测试尽可能多的实现版本,在尽可能多的CPU架构和尽可能多的不同系统大小上测试尽可能多的操作系统 正如您在中所看到的,即使只是测试另一个实现,也已经表明您的结论是错误的。(专业提示:永远不要基于1的样本大小进行概括!) 另一种选择是使用已经完成上述工作的库,如Todd回答中提到的库。他们做我上面提到的所有测试。他们还和各种实现的维护人员密切合作。例如,TruffleRuby的首席开发者Chris Seaton也是并发ruby的维护者之一,JRuby的首席开发者Charlie Nutter也是贡献者之一 但是上面的结果证明我错了 结果具有误导性。在Ruby中,
a+=1
是以下内容的简写:
a = a + 1
a+1
是赋值之前发生的方法调用。由于整数是Ruby中的对象,我们可以重写该方法:
module ThreadTest
def +(other)
super
end
end
Integer.prepend(ThreadTest)
上面的代码没有做任何有用的事情,它只是调用super
。但仅仅是在t之上添加一个Ruby实现
Integer.prepend(ThreadTest)
a = 0
Array.new(50){
Thread.new {
500_000.times { a += 1 }
}
}.each(&:join)
p "a: #{a}"
#=> "a: 11916339"