12

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

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 中线程是安全的?我正在使用红宝石 2.0.0p247

4

6 回答 6

14

这是因为由于 GIL(参见此处), MRI Ruby 线程并不是真正并行的,在 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 => 剪切...剪切...

有时同一只羊被剪两次毛!

于 2013-11-14T05:03:13.060 回答
1

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

Thread类派上用场的地方是当您编写的代码在 Ruby 领域之外进行时,例如,使用文件或网络 I/O、进行系统调用或通过绑定与 C 库进行交互。

于 2013-11-14T04:57:53.583 回答
1

这要归功于 Ruby 2.0 的Global Interpreter Lock

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

看:

于 2013-11-14T05:01:06.670 回答
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!
于 2015-03-25T14:14:32.807 回答
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.
于 2015-04-13T01:00:49.843 回答
-1

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

于 2013-11-14T05:24:29.237 回答