共享实例变量是否需要多个线程中的原子操作? (线程安全)

时间:2013-11-07 00:39:32

标签: ruby concurrency atomic

下面的代码是否是线程安全的?

@total_count = 0
t = []
100.times do
  t << Thread.new do
    100.times do
       @total_count += 2
    end
  end
end
t.map(&:join)
p @total_count

它似乎有效,但是为什么你不需要实现原子操作,因为所有线程都写入同一个实例变量。 Ruby中的实例变量是否通过原子操作原子实现?

1 个答案:

答案 0 :(得分:2)

不,代码不是线程安全的。由于GIL / GVL(全局解释器锁定/全局VM锁定)的影响,它在某些Ruby解释器(例如Ruby 2.0(MRI / KRI))中只有这样的外观。在这种情况下,它可能会产生您期望的结果(20000) - 但这种方法通常不能保证安全。

在JRuby解释器中运行代码,该解释器没有GIL并以真正的并行方式运行线程,在不同的运行中给出不同的结果,例如:

jruby-1.7.6 :011 > p @total_count
16174
 => 16174 

@total_count += 2扩展为@total_count = @total_count + 2,这意味着线程可以在RHS读取和LHS写入之间交错。这意味着总数可能(并且经常)比预期总数少(但在这种情况下,不会更多)。通过使用Mutex

,可以使代码成为线程安全的
@mutex = Mutex.new
@total_count = 0
t = []
100.times do
  t << Thread.new do
    100.times do
      @mutex.synchronize do
        @total_count += 2
      end
    end
  end
end
t.map(&:join)
p @total_count

在JRuby中,那总是产生预期的结果:

jruby-1.7.6 :014 > p @total_count
20000
 => 20000 

我意识到这与你的问题不同(我理解的是+=是否是原子的),但是即使在Ruby 2.0(MRI / KRI)中也可以通过扩展赋值和插入来证明原理小睡觉:

@total_count = 0
t = []
100.times do
  t << Thread.new do
    100.times do
      total_count = @total_count
      sleep 0.0000001
      @total_count = total_count + 2
    end
  end
end
t.map(&:join)
p @total_count

在Ruby 2.0中,一个可能的运行产生:

2.0.0p247 :013 >     p @total_count
512
 => 512 

但是使用Mutex

@mutex = Mutex.new
@total_count = 0
t = []
100.times do
  t << Thread.new do
    100.times do
      @mutex.synchronize do
        total_count = @total_count
        sleep 0.0000001
        @total_count = total_count + 2
      end
    end
  end
end
t.map(&:join)
p @total_count

产生正确的结果:

2.0.0p247 :224 > p @total_count
20000
 => 20000