亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Ruby 的多線程

本章節讓我們來學習 Ruby 的多線程。您將會了解到:什么是多線程,Ruby 中如何創建線程等知識。

1. Ruby 中的線程

通俗一點來講,線程可以讓程序同時執行多項操作。

比如:讀取多個文件、處理多個請求、建立多個API連接。多線程可以更好地利用CPU的核心,CPU的一個核好比一個普通人,一個普通人只能干一件事,多個人可以分開干不同的事或干很多次同樣的事。

注意事項:

在MRI(Matz 的 Ruby 解釋器)中,這是運行 Ruby 應用程序的默認方式,只有在運行 I/O 綁定的應用程序時,您才能從線程中受益。由于存在 GIL(Global Interpreter Lock,是由編程語言解釋器線程持有的互斥鎖,以避免與其他線程共享不是線程安全的代碼。),因此存在此限制。對于一般的 Ruby 和 Python 應用,即使在多核處理器上運行,使用 GIL 的解釋器始終總是允許一次僅執行一個線程。

每個進程都有至少一個線程,您可以按需創建更多線程。

2. I/O 綁定應用程序

首先,我們需要討論 CPU 綁定和 I/O 綁定應用程序之間的區別。

I/O 綁定應用程序是需要等待外部資源的應用程序:

  • API請求;
  • 數據庫(查詢結果);
  • 磁盤讀取。

線程可以在等待資源可用時決定停止。

這意味著另一個線程可以運行并執行其任務,而不會浪費時間等待。

I/O 綁定應用程序的一個示例是 Web 爬蟲(crawler)。

對于每個請求,爬蟲都必須等待服務器響應,并且在等待時它什么也不能做。

您可以一次發出4個請求,并在它們返回時處理響應,這將使您更快地獲取頁面。

2.1 創建一個線程

您可以通過調用Thread.new創建一個新的Ruby線程。確保傳遞帶有該線程需要運行的代碼的塊。

實例:

Thread.new { puts "hello from thread" }

# ---- 輸出結果 ----

是不是很簡單。

但你會發現,線程沒有輸出內容,這是因為Ruby 不等待線程完成。

您需要在線程上調用join方法來修復上面的代碼。

實例:

Thread.new { puts "hello from thread" }.join

# ---- 輸出結果 ----
hello from thread

如果要創建多個線程,可以將它們放入數組中,并在每個線程上調用join。

實例:

Thread.new { puts "hello from thread1" }.join
Thread.new { puts "hello from thread2" }.join
Thread.new { puts "hello from thread3" }.join

# ---- 輸出結果 ----
hello from thread1
hello from thread2
hello from thread3

學習Ruby的線程時,我們要多參考 Ruby 線程的文檔。

2.2 線程與異常

如果線程內發生異常,它將在不停止程序或不顯示任何錯誤消息的情況下靜默死。

實例:

Thread.new { raise 'hell' }

# ---- 輸出結果 ----

為了進行調試,您可能希望程序在發生不良情況時停止運行。

為此,您可以將 Thread 上的以下標志設置為 true:

Thread.abort_on_exception = true

在創建線程之前,請確保設置此標志。

實例:

Thread.abort_on_exception = true
Thread.new { raise 'hell' }
sleep(1)
# ---- 輸出結果 ----
ruby.rb:2:in `block in <main>': hell (RuntimeError)

**注意事項:**這里需要增加sleep(1),否則不會拋出異常。

2.3 線程池

假設您要處理數百個項目,為每個項目啟動一個線程將破壞您的系統資源。

它看起來像這樣:

pages_to_crawl = %w( index about contact ... )
pages_to_crawl.each do |page|
  Thread.new { puts page }
end

如果這樣做,您將與服務器啟動數百個連接,因此這可能不是一個好主意。

一種解決方案是使用線程池。線程池使您可以在任何給定時間控制活動線程的數量。

您可以建立自己的池,但是我不建議你這樣去做,Ruby有一個Gem可以為您完成這個操作。

實例:

require 'celluloid'
class Worker
  include Celluloid
  def process_page(url)
    puts url
  end
end
pages_to_crawl = %w( index about contact products ... )
worker_pool    = Worker.pool(size: 5)
# If you need to collect the return values check out 'futures'
pages_to_crawl.each do |page|
   worker_pool.process_page(page)
end

這次只有5個線程在運行,完成后他們將選擇下一個項目。

2.4 資源競爭風險

您必須知道并發代碼存在一些問題,例如,線程容易出現資源競爭狀況,比如同一時刻操縱了一個變量。競爭條件是當事情發生混亂并弄亂時。

另一個問題是死鎖deadlock)這是當一個線程擁有對某個資源的獨占訪問權(使用互斥鎖(mutex)之類的鎖定系統)而從未釋放它時,這使得所有其他線程都無法訪問它。

為避免這些問題,最好避免使用原始線程,并堅持使用一些已經為您處理好細節的Gem。

3. 小結

本章節中我們學習到了如何使用 Ruby 來創建一個線程。如何讓創建的線程拋出異常,線程池是什么,線程中存在的風險有什么。