为什么是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引擎和代码序列(理论上)异步线程的方式。您得到的结果是一致的,因为每个线程中的每个循环只是增加a的当前值,而a不是块局部变量或线程局部变量。对于YARV虚拟机上的线程,一次只有一个线程在检查或设置a的当前值,但我不会说这是一个原子操作。这只是引擎在线程之间缺乏实时并发性以及Ruby虚拟机的底层实现的副产品

    如果您担心在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"