1. 创建线程
可以使用Thread.new调用来创建新线程,为它提供一个含有将要在新线程中运行的block,例如:
require 'net/http' pages = %w(www.baidu.com www.google.com) threads = [] for page_to_fetch in pages threads << Thread.new(page_to_fetch) do |url| h = Net::HTTP.new(url, 80) puts "Fetching: ${url}" resp = h.get('/', nil) puts "Got #{url}: #{resp.message}" end end threads.each {|thr| thr.join}
线程共享在它开始执行时就已经存在的所有全局变量、实例变量和局部变量。当第一个线程运行时,page_to_fetch被设置为"www.baidu.com",同时创建线程的循环仍然运行,第二次时,page_to_fetch被设置为"www.google.com"。如果使用page_to_fetch变量的第一个线程还没结束,它会突然开始使用新的值。不过,在线程block里创建的局部变量对线程来说是真正的局部变量,每个线程会有这些变量的私有备份。
当Ruby程序终止时,不管线程状态如何,所有线程都被杀死。可以通过调用Thread#join方法来等待特定线程的正常结束。如果不想永远阻塞,可以给予join一个超时参数:如果超时在线程终止之前到期,join返回nil。join的另一个变种,Thread#value方法返回线程执行的最后语句的值。
除了join之外,还有一些便利函数。使用Thread.current总是可以得到当前线程,可以使用Thread.list得到一个所有线程的列表,可以使用Thread#status和Thread#alive?去确定特定线程的状态,另外,可以使用Thread#priority=调整线程的优先级。
如果需要线程局部变量能被别的线程访问,Thread类提供了一种特别措施,允许通过名字来创建和访问线程局部变量,例如:
count = 0 threads = [] 10.times do |i| threads[i] = Thread.new.do Thread.current["mycount"] = count count += 1 end end threads.each {|t| t.join; print t["mycount"], ","} puts "count = #{count}"
如果线程引发了末处理的异常,它的反应依赖于abort_on_exception标志和解释器debug标志。如果abort_on_exception是false,debug标志没有启用(默认),未处理的异常会简单杀死当前线程,而其他线程继续运行。如果设置abort_on_exception为true,或使用-d选项去打开debug标志,未处理的异常会杀死所有正在运行的线程。
2. 线程调度器
Thread类提供了若干种控制线程调度器的方法。调用Thread.stop停止当前线程,调用Thread#run安排运行特定的线程。Thread.pass把当前线程调度出去,允许运行别的线程,Thread#join和Thread#value挂起调用它们的线程,直到指定的线程结束为止,例如:
class Chaser attr_reader :count def initialize(name) @name = name @count = 0 end def chase(other) while @count < 5 while @count-other.count > 1 Thread.pass end @count += 1 print "#@name: #{count}\n" end end end c1 = Chaser.new("A") c2 = Chaser.new("B") threads = [ Thread.new{Thread.stop; c1.chase(c2)}, Thread.new{Thread.stop; c2.chase(c1)} ] start_index = rand(2) threads[start_index].run threads[1-start_index].run threads.each {|t| t.join}
3. 互斥
最底层的阻止其他线程运行的方法是使用全局的线程关键条件,当条件被设置为true(使用Thread.critical=)时,调度器将不会调度现有的线程去运行。但是这不会阻止创建和运行新线程。使用Thread.critical=不是很方便,Ruby有多种变通方法。
监视器用同步函数对包含一些资源的对象进行了封装,例如:
require 'monitor' class Counter < Monitor attr_reade :count def initialize @count = 0 super end def tick synchronize do @count +=1 end end end c = Counter.new t1 = Thread.new{100.times{c.tick}} t2 = Thread.new{100.times{c.tick}} t1.join t2.join
通于特定的监视器对象,每次只有一个线程能够执行synchronize block中的代码,所以再也不会有两个线程同时缓存中间结果。
线程库中的Queue类实现了一个线程安全的队列机制,多个线程可以添加和从队列中删除对象,保证了每次添加和删除是原子性的操作。
4. 多进程
有好几种方式可以衍生单独的进程,最简单的方式是运行一些命令,然后等待它结束。Ruby使用system命令和反引号方法来实现它,例如:
system("tar xzf test.tgz") result = `date`
Kernel.system方法在子进程中执行给定的命令,如果命令被找到和正确执行了,它会返回true,否则返回false。如果失败了,子进程的退出码保存在全局变量$?中。
很多情况下需要更多的控制:跟子进程进行对话,发送数据给它和从它那儿得到数据。IO.popen方法可以这么做。popen方法在子进程里运行命令,把子进程的标准输入和标准输出连接到Ruby IO对象,例如:
pig = IO.popen("/usr/local/bin/pig", "w+") pig.puts "ice cream after they go to bed" pig.close_write puts pig.gets
popen还有另一个值得注意的地方,如果被传递的命令是单个减号"-",popen会创建新的Ruby解释器,这个解释器和原先的解释器在popen返回后都会继续运行。原先的进程会接受到IO对象,而子进程得到nil,例如:
pipe = IO.popen("-", "w+") if pipe pipe.puts "Get a job!" STDERR.puts "Child says '#{pipe.gets.chomp}'" else STDERR.puts "Dad says '#{gets.chomp}'" puts "OK" end
kernel.fork调用会在父进程中返回进程ID,在子进程中返回nil,例如:
exec("sort testfile > output.txt") if fork.nil? Process.wait
调用Process.wait可以等待sort结束,同时会返回它的进程ID。如果需要在子进程结束时收到通知而不是等待,可以使用Kernel.trap设置信号处理方法,例如:
trap("CLD") do pid = Process.Wait puts "Child pid #{pid}: terminated" end exec("sort testfile > output.txt") if fork.nil?