question on threads

J

Jean G.

Hello,

count = 0
threads = []

10.times do |i|
threads = 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.
 
R

Robert Klemme

2010/3/9 Jean G. said:
Hello,

count =3D 0
threads =3D []

10.times do |i|
=A0threads =3D Thread.new do
=A0 =A0sleep(rand(0.1))
=A0 =A0Thread.current["mycount"] =3D count
=A0 =A0count +=3D 1
=A0end
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


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

Brian Candler

Jean said:
Hello,

count = 0
threads = []

10.times do |i|
threads = 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.
 
R

Robert Klemme

2010/3/9 Brian Candler said:
Jean said:
Hello,

count =3D 0
threads =3D []

10.times do |i|
=A0 threads =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/
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,965
Messages
2,570,148
Members
46,710
Latest member
FredricRen

Latest Threads

Top