2010/3/9 Brian Candler <b.candler / pobox.com>:
> Jean G. wrote:
>> Hello,
>>
>> count =3D 0
>> threads =3D []
>>
>> 10.times do |i|
>> =A0 threads[i] =3D Thread.new do
>> =A0 =A0 sleep(rand(0.1))
>> =A0 =A0 Thread.current["mycount"] =3D count
>> =A0 =A0 count +=3D 1
>> =A0 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
>
> =A0count +=3D 1
>
> is really a shorthand for
>
> =A0count =3D count + 1
>
> which is basically:
> =A0- read value of count
> =A0- add one to this value
> =A0- 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 =3D Mutex.new
count =3D 0

threads =3D (1..10).map do |i|
  Thread.new do
    sleep(rand(0.1))

    lock.synchronize do
      count +=3D 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 =3D Mutex.new
count =3D 0

(1..10).map do |i|
  Thread.new do
    sleep(rand(0.1))

    lock.synchronize do
      count +=3D 1
    end
  end
end.each do |th|
  puts th.value
end


--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/