为什么Ruby中没有竞争条件

时间:2013-11-14 04:26:32

标签: 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

6 个答案:

答案 0 :(得分:13)

这是因为MRI Ruby线程由于GIL而不是真正的并行(参见here),在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

一个很好的竞争条件示例是here,下面复制完整性

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 =>剪切...   剪切...

     

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

答案 1 :(得分:1)

Ruby有一个global interpreter lock。在Ruby 中发生的所有事情基本上是同步的。因此,您在Java等低级语言中遇到的引用问题 - 其中两个线程可能读取相同的值并在+=上相互冲突 - 不是问题。

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

答案 2 :(得分:1)

这将归功于Ruby 2.0的 Global Interpreter Lock

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

请参阅:

答案 3 :(得分:1)

在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!

答案 4 :(得分:0)

我也试图理解这一点,并且在此代码中从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 e.g. 1,573,313 or 1,493,791 etc
p c.tmp # Also varies: 882,928 etc.

答案 5 :(得分:-1)

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