为什么Ruby中没有竞争条件

我正在尝试multithreading示例。 我正在尝试使用以下代码生成竞争条件。 但我总是得到相同(正确)的输出。

class Counter attr_reader :count def initialize @count = 0 end def increment @count += 1 end def decrement @count -= 1 end end c = Counter.new t1 = Thread.start { 100_0000.times { c.increment } } t2 = Thread.start { 100_0000.times { c.increment } } t1.join t2.join p c.count #200_0000 

我能够在每个线程中使用少得多的迭代次数来观察Java中的竞争条件。 是不是我没有足够多次运行它来产生竞争条件,或者+ / -是Ruby中的线程安全吗? 我使用的是ruby 2.0.0p247

这是因为MRI Ruby线程由于GIL而不是真正的并行(参见此处 ),在CPU级别它们一次执行一个。

线程中的每个命令一次执行一个,因此每个线程中的@count总是正确更新。

可以通过添加另一个变量来模拟竞争条件:

 class Counter attr_accessor :count, :tmp def initialize @count = 0 @tmp = 0 end def increment @count += 1 end end c = Counter.new t1 = Thread.start { 1000000.times { c.increment; c.tmp += 1 if c.count.even?; } } t2 = Thread.start { 1000000.times { c.increment; c.tmp += 1 if c.count.even?; } } t1.join t2.join p c.count #200_0000 p c.tmp # not 100_000, different every time 

这里给出了一个很好的竞争条件的例子,下面复制完整性

 class Sheep def initialize @shorn = false end def shorn? @shorn end def shear! puts "shearing..." @shorn = true end end sheep = Sheep.new 5.times.map do Thread.new do unless sheep.shorn? sheep.shear! end end end.each(&:join) 

这是我在MRI 2.0上多次运行时看到的结果。

$ ruby​​ check_then_set.rb =>剪毛……

$ ruby​​ check_then_set.rb =>剪切…剪切……

$ ruby​​ check_then_set.rb =>剪切…剪切……

有时同样的羊被剪了两次!

Ruby有一个全局解释器锁 。 Ruby中发生的一切基本上是同步的。 因此,您所引用的问题是您在较低级别的语言(例如Java)中遇到的问题,其中两个线程可能读取相同的值并在+=上相互冲突 – 不是问题。

Thread类派上用场的地方在于你编写的代码可以处理Ruby之外的内容,例如,使用文件或网络I / O,进行系统调用,或通过绑定与C库连接。

这将归功于Ruby 2.0的Global Interpreter Lock

简而言之,由于Ruby解释器的底层实现,任何非IO的操作(例如文件读/写)都将同步发生。

看到:

在Ruby中看到竞争条件的非常简单的方法:

 i = 0 2.times do Thread.new do 30_000_000.times do # this should take more than 100ms a = i + 1 i = a end end end puts i # the value will always be different 

没有竞争条件的例子:

 i = 0 2.times do Thread.new do 10_000.times do # this should take less than 100ms a = i + 1 i = a end end end puts i # 20000, always! 

 i = 0 2.times do Thread.new do 30_000_000.times do # it doesn't matter how much time it takes i += 1 end end end puts i # 60000000, always! 

我也试图理解这一点,并在此代码中获得c.count的不同结果(从上面复制)。 例如,我得到c.coint = 1,573,313或1,493,791等。看看代码,似乎c.count每次应该是2,000,000!

 class Counter attr_accessor :count, :tmp def initialize @count = 0 @tmp = 0 end def increment @count += 1 end end c = Counter.new t1 = Thread.start { 1_000_000.times { c.increment; c.tmp += 1 if c.count.even?; } } t2 = Thread.start { 1_000_000.times { c.increment; c.tmp += 1 if c.count.even?; } } t1.join t2.join p c.count # Varies eg 1,573,313 or 1,493,791 etc p c.tmp # Also varies: 882,928 etc. 

对于Java,您只能在异步线程中获取竞争条件。找到您需要的确切解决方案可能很有用。