How to Return a Value from a Thread in Ruby

How can I return a value from a thread in Ruby?

The script

threads = []
(1..5).each do |i|
threads << Thread.new { Thread.current[:output] = `echo Hi from thread ##{i}` }
end
threads.each do |t|
t.join
puts t[:output]
end

illustrates how to accomplish what you need. It has the benefit of keeping the output with the thread that generated it, so you can join and get the output of each thread at any time. When run, the script prints


Hi from thread #1
Hi from thread #2
Hi from thread #3
Hi from thread #4
Hi from thread #5

How to get the value, who first returned one of the threads

I just delete t1.join, t2.join and t3.join...Becouse method .join stop main thread. But don't know why result time >= 1 and <=2. I think sleep instuction not been executed, becouse main thread just kill second when a = 1 in while loop without waiting sleep insturuction.
Final code:

require 'benchmark'
a = nil
puts "I'm need 1 second for test"
result_time = Benchmark.measure {
while true
t1 = Thread.new { a = nil unless a } unless t1
t2 = Thread.new { a = 1 unless a; sleep 1 } unless t2
t3 = Thread.new { a = 2 unless a; sleep 2 } unless t3
if !a.nil?
t1.kill if t1
t2.kill if t2
t3.kill if t3
break
end
end
}.real

if result_time >= 2.0
puts "Bad. More than 1 second. result_time: #{result_time}. a: #{a}"
else
puts "Cool. Less than 2 seconds. result_time: #{result_time}. a: #{a}"
end

Returned me:

I'm need 1 second for test
Cool. Less than 2 seconds. result_time: 0.001799600024241954. a: 1

Pass the result of a Ruby Thread as a parameter to a method

Threads don't have return values (except the Thread object itself).

This is a really common issue in javascript, where you use many async functions (such as setTimeout) that don't produce return values in the same way that synchronous functions do.

What Javascript typically uses to deal with this problem are callbacks (as well as promises/async/await). You can use callbacks in ruby in the form of blocks:

  class AsyncClass
attr_reader :foo, :bar
def async_method(&callback)
Thread.new do
@foo = 1
@bar = @foo + 1
callback.call(self)
end
end
end

Although you can call join on any thread to halt the code execution until it completes, this kind of removes the purpose of using a thread altogether (unless you are doing parallel processing). So, any method that calls async_method will need to be asynchronous itself.

This won't work in something like a controller action, which is synchronous (unless you are using a streaming response or server-push, but I'll assume you're not).

It's sort of a 'law' of async code that you cannot call an async function from a synchronous one, and get the 'return value', without calling join (and forcing it to run synchronously). So whenever you want the 'return value' of an async function, you need to make the caller function async, and use a block/callback to read the result. Note that this annoying process is sometimes called 'callback hell' in javascript and is why they implemented promises/async/await (which, by the way, seem to be supported by the ruby-concurrency gem).

But, say you were calling this method from another async method:

  class OtherClass
def initialize
AsyncClass.new.async_method do |async_class_inst|
puts async_class_inst.bar
end
sleep 1
end
end

OtherClass.new
# prints 2

In your case it would look like this:

  def student_progress_variables(student_email, &blk)
dashboard = Dashboard.new
Thread.new do
@projects = dashboard.obtain_projects
@current_student = obtain_student_with_email(student_email)

@reviews = obtain_selected_student_project_reviews(@current_student)
@requests = obtain_student_code_review_requests(@current_student).sort_by do |request|
request[obtain_code_review_requests_id]
end

@review_completions = obtain_student_code_review_completions(@current_student)
@courses = obtain_projects_courses(@projects.join)
blk.call(self)
end
end

But, again, you can only call this from another async method:

  class AsyncCaller
def initialize(&callback)
SomeClass.new.student_progress_variables("student@email.com") do |some_class_inst|
callback.call(self, some_class_inst)
end
sleep 1
end
end

AsyncCaller.new do |async_caller_inst, some_class_inst|
# .... do things here, thread is done
end

Note that in these examples, I'm passing self to the callback, so that it gets assigned to a block variable (e.g. async_class_inst). This is because the value of self changes depending on where you call the block from, as you can see in this example:

  class A
def initialize(&blk)
blk.call
sleep 1
end
end

A.new { puts self }
# prints 'main'

So, if you're making some manipulation on self in the thread (as you are, by setting instance variables), it's good practice to explicitly pass self to the callback/block, so you don't have to assume that the caller has access to it otherwise.

Also, please don't actually put sleep calls in your code, I'm only using these so if you run the snippets as scripts it will work. In actual code, you should use Thread#join if you want to wait until a thread is finished.

To reiterate, you should not use threads in your controller actions if you expect to get the results in time to include in the response (unless you are doing parallel processing with .join at the end).

How to handle thread returns Ruby

Two things, first, when you pass the 'x' into the thread, it's safer to make it thread-local by changing this:

threads << Thread.new { a,b = splat x }

Into this:

threads << Thread.new(x) { |x| a,b = splat x }

Next, to get the return value out, you join using :value.

So here's a quick demo I whipped up:

dummy = [
['a.txt', 'b.txt'],
['c.txt', 'd.txt'],
['e.txt', 'f.txt'],
['g.txt', 'h.txt'],
['i.txt', 'j.txt'],
['k.txt', 'l.txt']
]

threads = dummy.map do |pair|
Thread.new(pair) { |val| val }
end

vals = threads.map(&:value) # equiv. to 'threads.map { |t| t.value }'
puts vals.inspect

Crib off that and it should get you where you want to go.

ruby threading output

I thought when you run a ruby script, it waits until all threads/processes are done before terminating and returning?

Nope! From the documentation for Thread:

If we don't call thr.join before the main thread terminates, then all other threads including thr will be killed.

So you’ll need to join all of them:

threads = 10.times.map do
Thread.new do
puts 'Hello, Ruby!'
end
end

threads.each &:join

Rails and threading

Am I doing this right?

There are no issues in your code but I don't think that using threads makes a lot of sense in your code example since you're executing requests one after another anyway.
If you want to make parallel requests then you should do it like this instead:

threads = [params1, params2, ...].map { |p| Thread.new { api_call(p) } }
values = threads.map(&:value)

Am I doing this right? Or am I instead supposed to call join on those threads somewhere?

Both join and value calls will wait for a thread to finish but value is more convenient for you there if you want to retrieve a value returned from a thread. value is using join under the hood.

Is this safe for production or is there something I should be tweaking, maybe in the Nginx or passenger configs?

You don't need to tweak anything to use threads and it is generally safe to use them in production (if you're using MRI then GIL prevents deadlocks). You just need to be aware that if you're using a lot of threads then you'll be using a lot of extra memory. And using threads don't always improve performance of a program. For example, due to GIL there is not much point in using threads for executing CPU-intensive code even on a multicore machine.

ruby local thread variable access from other methods

This seems like you'll trip yourself up a lot. It might be better to initialize a new object for each thread.

class Tour
def self.destinations
threads = []

[:new_york, :london, :sydney].each do |city|
threads << Thread.new { Destination.new(city).go }
end

threads.each(&:join)
end
end

class Destination
attr_reader :location

def initialize(location)
@location = location
end

def go
puts "I am going to visit #{location}."
end
end

# Tour.destinations

Suggested reading: https://blog.engineyard.com/2011/a-modern-guide-to-threads



Related Topics



Leave a reply



Submit