This is because MRI Ruby threads are not really parallel due to GIL (see here), at CPU level they are executed one at a time.
Each command in a thread is executed one at a time hence @count
in each thread is always updated correctly.
Race condition can be simulated by adding another variable like:
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
A nice example of race condition is given here, copied below for completeness
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)
Here's the result I see from running this on MRI 2.0 several times.
$ ruby check_then_set.rb => shearing...
$ ruby check_then_set.rb => shearing... shearing...
$ ruby check_then_set.rb => shearing...
shearing...
Sometimes the same sheep is being shorn twice!
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…