Run blocks asynchronously with little effort. A Ruby equivalent for JS setTimeout
.
This tiny lib was written to slightly reduce the amount of scaffolding required to do threads in Ruby. It is aimed at simpler use cases, for more complicated scenarios using vanilla thread syntax is recommended.
I hope Branch� can save some frustration for people who discover that implementing a straightforward JS setTimeout(function() { ... }, 0)
with Ruby is much more complicated and less intuitive. On the other hand, Branch is not an escape from getting to know how threads work.
- First of all, wrap your code with the
Branch.new { ... }
wrapper. - Within that wrapper, the
branch { ... }
method is available. It starts the passed block in a new asynchronous thread. - The end of the
Branch.new { ... }
wrapper will wait for all the threads to finish. In other words, the end of wrapper synchonizes your code. If you start anotherBranch.new { ... }
afterwards, it will start sequentially after the previous branch is complete. - To start a thread with a delay, use
branch(2) { ... }
. This example will be executed after two seconds, without blocking the main thread. - If you need to synchronize a certain operation in order to prevent a race condition (JS coders beware!), you can use a mutex like this:
b.branch do |mutexes|
mutexes[:meaningful_mutex_name].synchronize { ... }
end
- You don't need to instantiate mutexes manually. To reuse a mutex in another thread, simply access the
mutexes
hash with the same key. You can use anything for keys, e. g. integers, but a meaningful symbol is recommended. - Within a
branch
, you can use as many mutexes as you need.
Please look into these pieces of code to see how Branch usage compares to vanilla syntax
Requires a fair amount of scaffolding:
- A thread array is to be defined manually.
- Each thread should be added to that array.
- Threads should be joined so that the main script doesn't exit before all threads finish.
threads = []
puts "Starting threads"
threads << Thread.new { sleep 5; puts "Thread 1 finished." }
threads << Thread.new { sleep 2; puts "Thread 2 finished." }
threads << Thread.new { sleep 1; puts "Thread 3 finished." }
puts "All threads started."
threads.each { |t| t.join }
puts "All threads complete."
All the scaffolding you need is you need is a Branch.new { }
wrapper.
Branch.new do |b|
puts "Starting threads"
b.branch { sleep 5; puts "Thread 1 finished." }
b.branch { sleep 2; puts "Thread 2 finished." }
b.branch { sleep 1; puts "Thread 3 finished." }
puts "All threads started."
end
puts "All threads complete."
All mutexes have to be instantiated manually.
threads = []
mutex_array_access = Mutex.new
mutex_database_transaction = Mutex.new
mutex_coffee_brewing = Mutex.new
10.times do
threads << Thread.new do
# Imitating a time-consuming operation
sleep rand(1..5)
mutex_array_access .synchronize { shared_array << 'foo' if shared_array.length < 5 }
mutex_database_transaction.synchronize { DB::send('foo', 888) { |foo| foo.bar }}
mutex_coffee_brewing .synchronize { coffee_machine.clean.fill('water').make_coffee }
end
end
threads.each { |t| t.join }
Mutexes are instantiated automatically when they're first used and will persist throughuout the Branch.new{ }
wrapper.
Branch.new do |b|
10.times do
b.branch do |mutexes|
# Imitating a time-consuming operation
sleep rand(1..5)
mutexes[:array_access] .synchronize { shared_array << 'foo' if shared_array.length < 5 }
mutexes[:database_transaction].synchronize { DB::send('foo', 888) { |foo| foo.bar }}
mutexes[:coffee_brewing] .synchronize { coffee_machine.clean.fill('water').make_coffee }
end
end
end