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?