Multiple Threads and Processes in Ruby
December 16, 2007
Filed under Ruby
Tags: external commands, processes, Ruby, threads
Threads
At the moment (ruby version 1.8 ) one can either branch off a different thread, which will be handled by the same ruby interpreter as the parent. Which means that all variables created outside before the branching are still shared and changes made by one thread will be reflected in another thread.
From the OS point of view there is only one thread, therefore there might be issues with fairness properties of these threads.
t = Thread.new() {
# code for the child thread goes here
}
t.join # this tell the parent thread
# to block here and wait for t.
Processes
Another way of executing 2 things at once is to branch off a completely new ruby interpreter, which can be easily done with Kernel::fork. With this approach all variables are copied and therefore changes made by one process are not seen by the other.
pid = fork {
# Child process code goes here
}
# Either of these should be used, if none is called the OS
# might accumulate zombie processes:
Process.waitpid(pid) # Parent will block here
# and wait for the child
Process.waitpid(pid, Process::WNOHANG) # Will not block
# here, if no child is there at the moment
Process.detach(pid) # You are not interested in the exit
# code of this child and it should become
# independent
The interesting thing is that fork copies not the whole state of the interpreter, but just the current Thread, so if one had 2 Threads running already and made a fork call he would have 2 processes: 1 with 2 Threads(parent) and 1 with 1 Thread(child).
External Programs
External Programs in ruby can be run in various ways:
exec
The simples case, replaces the current process with the given command
# echoes list of files # in current directory exec "echo *" # never get here
system
Executes the given command in a subshell, returns true/false depending on the exit code and directsthe output of the program to the standard streams.
system( "echo *" )
`cmd` or %x{..}
Executes the given command in a subshell and returns the output
`date`
IO.popen
Has quite a complex signature, but essentially it makes a different process, executes the command in it and makes a pipe connection to the parent, therefore the parent can listen to the output of the program and provide it with input on the spot.If one provides a ‘-’ string as parameter, the child process will be a ruby one, similar to fork, but still with the pipe connecting the child and the parent.
IO.popen( "-" ) {|f|
$stderr.puts "#{Process.pid} is here, f is #{f}"}
One thing to watch out for is that when supplying the ‘-’ parameter the fork is done on the OS level, which means that this does not just copy the current thread as fork, but copies the whole interpreter. Therefore if one has a few threads running in the parent, their copies will be present in the child. This can be worked around by separating out the current thread with a ruby level fork
pid = fork {
IO.popen( "-" ) {|f|
$stderr.puts "#{Process.pid} is here, f is #{f}"}
}
waitpid(pid)
Open3.popen3
Similar to IO.popen, but provides an error output stream as well.Does not support ‘-’, therefore only truly external commands can be run (not ruby code)
require 'open3'
Open3.popen3(cmd) { |stdin, stdout, stderr| ... }
Daemon Process
To make you ruby code run as a daemon (stay alive after the parent has terminated) you should include these lines:
exit if fork # Parent exits, child continues. Process.setsid # Become session leader. exit if fork # Zap session leader. # After this point you are in a daemon process
The 2 exits are mandatory
Resources
http://pleac.sourceforge.net/pleac_ruby/processmanagementetc.html
http://blog.jayfields.com/2006/06/ruby-kernel-system-exec-and-x.html