[Ruby]線程和進程

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?



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章