AdamF
AdamF

Reputation: 197

Ruby Variable Reference Issue

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

Answers (2)

sawa
sawa

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

brymck
brymck

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

Related Questions