8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

为什么 Ruby 中不存在竞争条件

relent95 1月前

52 0

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

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

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

帖子版权声明 1、本帖标题:为什么 Ruby 中不存在竞争条件
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由relent95在本站《ruby》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 我期望这是因为你总是在增加计数器。c 的值对于两个线程都位于相同的位置,并且可以在那里增加。一个有趣的测试是分别对每个线程进行计时,然后一起对它们进行计时。这是你所期望的吗?只是提出一些想法

  • JRuby (9.1.13) 怎么样?我以为它应该提供真正的线程 (而不是使用 GIL),但我得到的结果与 MRI 2.4.1 相同 (即,除非我按照下面的答案模拟竞争,否则计数一致)。

  • 搬起石头砸自己的脚。我使用 shebang 运行代码,所以实际上并没有使用 JRuby。有一次我通过 rvm 切换到 JRuby 并执行

  • 这是因为 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 => 剪切... 剪切...

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

  • 如果我理解正确的话,向增量方法添加更多语句也应该会产生竞争条件,对吗?我刚刚尝试过,但我无法产生它。

  • 通过在原始代码中添加额外语句而不改变变量的访问方式,不会产生竞争条件。问题不在于每个线程所花费的时间。

  • Y123 1月前 0 只看Ta
    引用 8

    一个非常简单的补充:自 1.9.3 版以来,Ruby 线程的时间片通常为 100 毫秒。这意味着 100,000 次简单代码迭代很少会触发竞争条件。线程甚至不会并发运行。

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

    该类 Thread 就派上用场了,例如,使用文件或网络 I/O、进行系统调用或通过绑定与 C 库交互。

  • @NeilSlater:您是说 Ruby 2.0 取消了 GIL 吗?我没在任何地方看到过这一点(虽然不是说您错了——我可能只是没注意到)。如果能提供参考就更好了。

  • 不,所以用了 +,但我可能可以更清楚一点。这仅适用于 Ruby MRI 1.9.x 和 2.0.0。我测试了所有安装版本的 OP 代码。MRI 1.9.3 和 2.0.0 的行为符合问题,但其他版本则不然。事实上,大多数主要的 Ruby 版本(可能与大多数正在使用的 Ruby 版本相反)的行为并不像 OP 所经历的那样。

  • 这是由于 Ruby 2.0 的 全局解释器锁 .

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

    看:

    • Ruby Enterprise 是否使用绿色线程?
    • http://merbist.com/2011/10/03/about-concurrency-and-the-gil/
    • http://www.jstorimer.com/blogs/workingwithcode/7766043-interview-brian-shirai-on-rubinius-2-0-the-gil-and-thread-safe-ruby-code
  • 引用 13

    我还建议添加优秀的 jstorimer.com/blogs/workingwithcode/…

  • 查看 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!
    
  • 如果我在 Ruby 1.9.3 上运行此代码,我可以获得竞争条件,但在 Ruby 2.4.1 上运行则不会。如果我使用 JRuby 9.13(而且奇怪的是,它比任何版本都慢),即使是简单的单一赋值也会变得不确定。所以我学到的教训是,如果你要使用线程做任何事情并关心预期的行为和性能,你最好真正了解你的 Ruby 版本以及它如何处理线程。

  • 引用 16

    我也在尝试理解这一点,但在这段代码(从上面复制)中,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.
    
  • 对于 Java,您只能在异步线程中获取竞争条件。找到您需要的确切解决方案可能会很有用。

返回
作者最近主题: