下面的代码是线程安全的吗?
@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 中的实例变量是用原子操作原子实现的吗?
下面的代码是线程安全的吗?
@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 中的实例变量是用原子操作原子实现的吗?
不,代码不是线程安全的。由于 GIL/GVL(全局解释器锁定/全局 VM 锁定)的影响,它只在某些 Ruby 解释器(例如 Ruby 2.0 (MRI/KRI))中出现。在这种情况下,它可能会产生您期望的结果(20000
)——但这种方法通常不能保证安全。
在没有 GIL 并真正并行运行线程的 JRuby 解释器中运行代码,在不同的运行中会给出不同的结果,例如:
jruby-1.7.6 :011 > p @total_count
16174
=> 16174
@total_count += 2
扩展为@total_count = @total_count + 2
,意味着线程可以在 RHS 读取和 LHS 写入之间交错。这意味着总数可能(并且通常会)少于(但在这种情况下,不会超过)预期的总数。可以通过使用 a 使代码成为线程安全的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
但是,使用 a 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