Why are not ruby threads working as expected? [duplicate] - ruby

Why the result is not from 1 to 10, but 10s only?
require 'thread'
def run(i)
puts i
end
while true
for i in 0..10
Thread.new{ run(i)}
end
sleep(100)
end
Result:
10
10
10
10
10
10
10
10
10
10
10
Why loop? I am running while loop, because later I want to iterate through the DB table all the time and echo any records that are retrieved from the DB.

The block that is passed to Thread.new may actually begin at some point in the future, and by that time the value of i may have changed. In your case, they all have incremented up to 10 prior to when all the threads actually run.
To fix this, use the form of Thread.new that accepts a parameter, in addition to the block:
require 'thread'
def run(i)
puts i
end
while true
for i in 0..10
Thread.new(i) { |j| run(j) }
end
sleep(100)
end
This sets the block variable j to the value of i at the time new was called.

#DavidGrayson is right.
You can see here a side effect in for loop. In your case i variable scope is whole your file. While you are expecting only a block in your for loop as a scope. Actually this is wrong approach in idiomatic Ruby. Ruby gives you iterators for this job.
(1..10).each do |i|
Thread.new{ run(i)}
end
In this case scope of variable i will be isolated in block scope what means for each iteration you will get new local (for this block) variable i.

The problem is that you have created 11 threads that are all trying to access the same variable i which was defined by the main thread of your program. One trick to avoid that is to call Thread.new inside a method; then the variable i that the thread has access to is just the particular i that was passed to the method, and it is not shared with other threads. This takes advantage of a closure.
require 'thread'
def run(i)
puts i
end
def start_thread(i)
Thread.new { run i }
end
for i in 0..10
start_thread i
sleep 0.1
end
Result:
0
1
2
3
4
5
6
7
8
9
10
(I added the sleep just to guarantee that the threads run in numerical order so we can have tidy output, but you could take it out and still have a valid program where each thread gets the correct argument.)

Related

How to add scheduler with ruby loop?

I am trying to code a twitchbot.
I want to add a scheduler with for loop.
I tried that code but it‘s just printing !prima.By the way, scheduler's timer sume2 is working fine.
require "rufus-scheduler"
scheduler = Rufus::Scheduler.new
sume = ['!prime', 'sea', 'ase', '!prima']
sume2 = ['30s', '20s', '10s', '40s']
s3 = sume2.length - 1
for x in 0..s3
scheduler.interval sume2[x] do
puts sume[x]
end
end
What can I do about that?
Your problem here is you are using a for loop instead of an Enumerable method such as each.
Short answer, just change for x in 0..s3 to sume2.each_index do |x|
Longer answer, you can compare the following:
for x in 0..3
Thread.new { loop { puts(x); sleep 1 } }
end
with:
(0..3).each do |x|
Thread.new { loop { puts(x); sleep 1 } }
end
The first just prints 3 repeatedly, but the second prints 1,2,3 as intended
Why? It's because with the for loop. your x variable gets overwritten each loop. With each, your x variable is scoped to the block and you have a closure.
For more explanation, see Closures and for loops in Ruby, or https://scotch.io/tutorials/understanding-ruby-closures, or just google "closures in ruby"
Note, it's not idiomatic to use for loops in Ruby, for this particular reason

Aren't ruby Queues thread safe why is the queue not synchronizing?

I am trying to create many threads and return the result in a data structure and I read that Queue is thread-safe, but when I run the code it doesn't produce the expected result.
require 'thread'
class ThreadsTest
queue = Queue.new
threads = []
for i in 1..10
threads << Thread.new do
queue << i
end
end
threads.each { |t| t.join }
for i in 1..10
puts queue.pop()
end
end
The code prints: (always a little different)
4
4
4
4
10
10
10
10
10
10
I was expecting the numbers 1 through 10.
I have tried to synchronize it manually to no avail:
mutex = Mutex.new
for i in 1..10
threads << Thread.new do
mutex.synchronize do
queue << i
end
end
end
What am I missing?
Queue is thread-safe but your code is not. Just like variable queue, variable i is shared across your threads, so the threads refer to the same variable while it is being changed in the loop.
To fix it, you can pass the variable to Thread.new, which turns it into a thread-local variable:
threads << Thread.new(i) do |i|
queue << i
end
The i within the block shadows the outer i, because they have the same name. You can use another name (e.g. |thread_i|) if you need both.
Output:
3
2
10
4
5
6
7
8
9
1

How to call a method every X seconds in Rails? [duplicate]

For example, if I want to make a timer, how do I make a delay in the loop so it counts in seconds and do not just loop through it in a millisecond?
The 'comment' above is your answer, given the very simple direct question you have asked:
1.upto(5) do |n|
puts n
sleep 1 # second
end
It may be that you want to run a method periodically, without blocking the rest of your code. In this case, you want to use a Thread (and possibly create a mutex to ensure that two pieces of code are not attempting to modify the same data structure at the same time):
require 'thread'
items = []
one_at_a_time = Mutex.new
# Show the values every 5 seconds
Thread.new do
loop do
one_at_a_time.synchronize do
puts "Items are now: #{items.inspect}"
sleep 5
end
end
end
1000.times do
one_at_a_time.synchronize do
new_items = fetch_items_from_web
a.concat( new_items )
end
end
Somehow, many people think that putting a sleep method with a constant time interval as its argument will work. However, note that no method takes zero time. If you put sleep(1) within a loop, the cycle will surely be more than 1 second as long as you have some other content in the loop. What is worse, it does not always take the same time processing each iteration of a loop. Each cycle will take more than 1 second, with the error being random. As the loop keeps running, this error will contaminate and grow always toward positive. Especially if you want a timer, where the cycle is important, you do not want to do that.
The correct way to loop with constant specified time interval is to do it like this:
loop do
t = Time.now
#... content of the loop
sleep(t + 1 - Time.now)
end

Ruby threads and variable

Why the result is not from 1 to 10, but 10s only?
require 'thread'
def run(i)
puts i
end
while true
for i in 0..10
Thread.new{ run(i)}
end
sleep(100)
end
Result:
10
10
10
10
10
10
10
10
10
10
10
Why loop? I am running while loop, because later I want to iterate through the DB table all the time and echo any records that are retrieved from the DB.
The block that is passed to Thread.new may actually begin at some point in the future, and by that time the value of i may have changed. In your case, they all have incremented up to 10 prior to when all the threads actually run.
To fix this, use the form of Thread.new that accepts a parameter, in addition to the block:
require 'thread'
def run(i)
puts i
end
while true
for i in 0..10
Thread.new(i) { |j| run(j) }
end
sleep(100)
end
This sets the block variable j to the value of i at the time new was called.
#DavidGrayson is right.
You can see here a side effect in for loop. In your case i variable scope is whole your file. While you are expecting only a block in your for loop as a scope. Actually this is wrong approach in idiomatic Ruby. Ruby gives you iterators for this job.
(1..10).each do |i|
Thread.new{ run(i)}
end
In this case scope of variable i will be isolated in block scope what means for each iteration you will get new local (for this block) variable i.
The problem is that you have created 11 threads that are all trying to access the same variable i which was defined by the main thread of your program. One trick to avoid that is to call Thread.new inside a method; then the variable i that the thread has access to is just the particular i that was passed to the method, and it is not shared with other threads. This takes advantage of a closure.
require 'thread'
def run(i)
puts i
end
def start_thread(i)
Thread.new { run i }
end
for i in 0..10
start_thread i
sleep 0.1
end
Result:
0
1
2
3
4
5
6
7
8
9
10
(I added the sleep just to guarantee that the threads run in numerical order so we can have tidy output, but you could take it out and still have a valid program where each thread gets the correct argument.)

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
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.
"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)

Resources