Reputation: 197
I am not fluent in ruby and am having trouble with the following code example. I want to pass the array index to the thread function. When I run this code, all threads print "4". They should instead print "0 1 2 3 4" (in any order).
It seems that the num variable is being shared between all iterations of the loop and passes a reference to the "test" function. The loop finishes before the threads start and num is left equal to 4.
What is going on and how do I get the correct behavior?
NUM_THREADS = 5
def test(num)
puts num.to_s()
end
threads = Array.new(NUM_THREADS)
for i in 0..(NUM_THREADS - 1)
num = i
threads[i] = Thread.new{test(num)}
end
for i in 0..(NUM_THREADS - 1)
threads[i].join
end
Upvotes: 1
Views: 114
Reputation: 168101
"What is going on?" => The scope of num
is the main environment, so it is shared by all threads (The only thing surrounding it is the for
keyword, which does not create a scope). The execution of puts
in all threads was later than the for
loop on i
incrementing it to 4
. A variable passed to a thread as an argument (such as num
below) becomes a block argument, and will not be shared outside of the thread.
NUM_THREADS = 5
threads = Array.new(NUM_THREADS){|i| Thread.new(i){|num| puts num}}.each(&:join)
Upvotes: 1
Reputation: 7663
Your script does what I would expect in Unix but not in Windows, most likely because the thread instantiation is competing with the for loop for using the num
value. I think the reason is that the for
loop does not create a closure, so after finishing that loop num
is equal to 4
:
for i in 0..4
end
puts i
# => 4
To fix it (and write more idiomatic Ruby), you could write something like this:
NUM_THREADS = 5
def test(num)
puts num # to_s is unnecessary
end
# Create an array for each thread that runs test on each index
threads = NUM_THREADS.times.map { |i| Thread.new { test i } }
# Call the join method on each thread
threads.each(&:join)
where i
would be local to the map
block.
Upvotes: 1