For the code above, why the output numbers are random, rather than
from 0 to 9 by increasing?
Because there are no guarantees about thread scheduling. Btw, your
code is not really thread safe since you access a shared resource
without proper synchronization (although it might work on some Ruby
platforms).
For the code above, why the output numbers are random, rather than
from 0 to 9 by increasing?
Because:
(1) each thread sleeps for a random amount of time before capturing and
incrementing the value of ‘count’; but
(2) you join each thread in the order in which they were started.
Consider, for example, that threads[0] might sleep for 0.09 seconds, but
threads[1] might sleep for 0.02 seconds. Hence threads[1] will capture a
lower value than threads[0].
As has already been pointed out, this code is not threadsafe -
occasionally, two threads may capture the same value of ‘count’. That’s
because
count += 1
is really a shorthand for
count = count + 1
which is basically:
read value of count
add one to this value
store this value back to count
Thread X could get as far as reading the value of ‘count’ before it is
suspended; then thread Y could run, read the same value of ‘count’, and
increment it. Then thread X will be re-scheduled, and also increment and
save back the same value.
occasionally, two threads may capture the same value of ‘count’. That’s
add one to this value
store this value back to count
Thread X could get as far as reading the value of ‘count’ before it is
suspended; then thread Y could run, read the same value of ‘count’, and
increment it. Then thread X will be re-scheduled, and also increment and
save back the same value.
Brian, thank you for taking the time to do a more elaborate explanation.
One additional thing: since Ruby’s threads can actually return a value
we can rewrite the original piece to this version, which is also
thread safe:
lock = Mutex.new
count = 0
threads = (1…10).map do |i|
Thread.new do
sleep(rand(0.1))
lock.synchronize do
count += 1
end
end
end
threads.each do |th|
puts th.value
end
Note that #synchronize returns the value returned by the block and by
that way we return the result of incrementing as the thread’s return
value which is captured through Thread#value (which also joins the
thread).
Kind regards
robert
PS: We can make this even shorter, just for the fun of it - I don’t
really recommend that style:
lock = Mutex.new
count = 0
(1…10).map do |i|
Thread.new do
sleep(rand(0.1))
lock.synchronize do
count += 1
end
end
end.each do |th|
puts th.value
end
This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.