From: Robert Klemme Date: 2010-03-10T17:24:20+09:00 Subject: Re: question on threads 2010/3/9 Brian Candler : > Jean G. wrote: >> Hello, >> >> count = 0 >> threads = [] >> >> 10.times do |i| >>   threads[i] = Thread.new do >>     sleep(rand(0.1)) >>     Thread.current["mycount"] = count >>     count += 1 >>   end >> end >> >> threads.each {|t| t.join; print t["mycount"], ", " } >> >> >> 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. 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 -- remember.guy do |as, often| as.you_can - without end http://blog.rubybestpractices.com/