第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

Ruby 的多線程

本章節(jié)讓我們來學(xué)習(xí) Ruby 的多線程。您將會(huì)了解到:什么是多線程,Ruby 中如何創(chuàng)建線程等知識(shí)。

1. Ruby 中的線程

通俗一點(diǎn)來講,線程可以讓程序同時(shí)執(zhí)行多項(xiàng)操作。

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

注意事項(xiàng):

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

每個(gè)進(jìn)程都有至少一個(gè)線程,您可以按需創(chuàng)建更多線程。

2. I/O 綁定應(yīng)用程序

首先,我們需要討論 CPU 綁定和 I/O 綁定應(yīng)用程序之間的區(qū)別。

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

  • API請(qǐng)求;
  • 數(shù)據(jù)庫(kù)(查詢結(jié)果);
  • 磁盤讀取。

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

這意味著另一個(gè)線程可以運(yùn)行并執(zhí)行其任務(wù),而不會(huì)浪費(fèi)時(shí)間等待。

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

對(duì)于每個(gè)請(qǐng)求,爬蟲都必須等待服務(wù)器響應(yīng),并且在等待時(shí)它什么也不能做。

您可以一次發(fā)出4個(gè)請(qǐng)求,并在它們返回時(shí)處理響應(yīng),這將使您更快地獲取頁面。

2.1 創(chuàng)建一個(gè)線程

您可以通過調(diào)用Thread.new創(chuàng)建一個(gè)新的Ruby線程。確保傳遞帶有該線程需要運(yùn)行的代碼的塊。

實(shí)例:

Thread.new { puts "hello from thread" }

# ---- 輸出結(jié)果 ----

是不是很簡(jiǎn)單。

但你會(huì)發(fā)現(xiàn),線程沒有輸出內(nèi)容,這是因?yàn)?strong>Ruby 不等待線程完成。

您需要在線程上調(diào)用join方法來修復(fù)上面的代碼。

實(shí)例:

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

# ---- 輸出結(jié)果 ----
hello from thread

如果要?jiǎng)?chuàng)建多個(gè)線程,可以將它們放入數(shù)組中,并在每個(gè)線程上調(diào)用join。

實(shí)例:

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

# ---- 輸出結(jié)果 ----
hello from thread1
hello from thread2
hello from thread3

學(xué)習(xí)Ruby的線程時(shí),我們要多參考 Ruby 線程的文檔。

2.2 線程與異常

如果線程內(nèi)發(fā)生異常,它將在不停止程序或不顯示任何錯(cuò)誤消息的情況下靜默死。

實(shí)例:

Thread.new { raise 'hell' }

# ---- 輸出結(jié)果 ----

為了進(jìn)行調(diào)試,您可能希望程序在發(fā)生不良情況時(shí)停止運(yùn)行。

為此,您可以將 Thread 上的以下標(biāo)志設(shè)置為 true:

Thread.abort_on_exception = true

在創(chuàng)建線程之前,請(qǐng)確保設(shè)置此標(biāo)志。

實(shí)例:

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

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

2.3 線程池

假設(shè)您要處理數(shù)百個(gè)項(xiàng)目,為每個(gè)項(xiàng)目啟動(dòng)一個(gè)線程將破壞您的系統(tǒng)資源。

它看起來像這樣:

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

如果這樣做,您將與服務(wù)器啟動(dòng)數(shù)百個(gè)連接,因此這可能不是一個(gè)好主意。

一種解決方案是使用線程池。線程池使您可以在任何給定時(shí)間控制活動(dòng)線程的數(shù)量。

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

實(shí)例:

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個(gè)線程在運(yùn)行,完成后他們將選擇下一個(gè)項(xiàng)目。

2.4 資源競(jìng)爭(zhēng)風(fēng)險(xiǎn)

您必須知道并發(fā)代碼存在一些問題,例如,線程容易出現(xiàn)資源競(jìng)爭(zhēng)狀況,比如同一時(shí)刻操縱了一個(gè)變量。競(jìng)爭(zhēng)條件是當(dāng)事情發(fā)生混亂并弄亂時(shí)。

另一個(gè)問題是死鎖deadlock)這是當(dāng)一個(gè)線程擁有對(duì)某個(gè)資源的獨(dú)占訪問權(quán)(使用互斥鎖(mutex)之類的鎖定系統(tǒng))而從未釋放它時(shí),這使得所有其他線程都無法訪問它。

為避免這些問題,最好避免使用原始線程,并堅(jiān)持使用一些已經(jīng)為您處理好細(xì)節(jié)的Gem。

3. 小結(jié)

本章節(jié)中我們學(xué)習(xí)到了如何使用 Ruby 來創(chuàng)建一個(gè)線程。如何讓創(chuàng)建的線程拋出異常,線程池是什么,線程中存在的風(fēng)險(xiǎn)有什么。