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'Returned me:
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
}.realif 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
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 includingthr
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
Generate Ruby Classes from Xsd
Installing Gem Fails with Permissions Error
How to Return Multiple Tags from a Rails Helper
What Does the % Operator Do in Ruby in N % 2
Context Aware Authorization Using Cancan
Is Autoload Thread-Safe in Ruby 1.9
How to Use an Actionview::Helper in a Ruby Script, Outside of Rails
Rbenv Build Failed on Ubuntu 14.04
Ruby Remove Empty Lines from String
How to Mix a Module into an Rspec Context
How to Create Database from Schema.Rb Without Initializing Rails
Paperclip :Style Depending on Model (Has_Many Polymorphic Images)