Forum: Ruby question on threads

Posted by Jean G. (Guest)
on 2010-03-09 05:40
(Received via mailing list)
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?

Thanks.
Posted by Robert Klemme (Guest)
on 2010-03-09 09:12
(Received via mailing list)
2010/3/9 Jean G. <rubynewbee@gmail.com>:
>  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 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).

Kind regards

robert
Posted by Brian Candler (candlerb)
on 2010-03-09 17:43
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.
Posted by Robert Klemme (Guest)
on 2010-03-10 09:24
(Received via mailing list)
2010/3/9 Brian Candler <b.candler@pobox.com>:
>>     count += 1
>
> 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
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.