【黑暗執行緒】用 .NET 寫生產者消費者模式的好物 - BlockingCollection

2022/6/6

當代量產型製造業幾乎都是採用生產線作業,以汽車為例,會分成焊接、烤潻、組裝、測試等站,各站依處理時間調整人力與設備數量,以求站與站之間能無縫接軌,將閒置及等待時間降到最低,達到最大產能。而我們開發系統時,若遇到包含多個步驟的大量批次作業,每個步驟具有一定複雜度、涉及資源不一,此時就可借用生產線概念,實作成「生產者 vs 消費者」模式,追求最佳處理效能。

生產者消費者模式是電腦處理多步驟處理程序常用的軟體設計模式,也是作業系統課程一定會講到的觀念。在這個模式中,將處理動作分為「生產者」與「消費者」,前者會產出半成品或成品,由後者接手後續運用,例如:

任務 生產者 消費者
資料爬蟲 關鍵字搜索、下載內容 (網路 I/O) 內容分析 (CPU 運算)
照片匯入資料庫 識別日期、尺寸縮放、產生縮圖 (CPU 運算) 寫入資料庫 (磁碟 I/O)

在以上兩個案例中,性質相關的動作會歸在一起,方便資源調配,例如:下載內容常需等待網路傳回結果但不耗 CPU,我們可以多開幾條執行緒提高抓取速度;內容分析很吃 CPU,可依 CPU 核數決定執行緒數量使產能最大化。寫資料庫為循序作業假設每秒可寫入一筆,但圖檔處理吃 CPU 四秒才能完成一張,故生產者端可開四條執行緒平均每秒產生一張照片,以便與消費者的消化速度完美銜接。

生產者與消費者間需要一條 Queue,讓生產者將資料有效率且可靠地交給消費者接手處理,這條 Queue 最好能具備以下功能:

  1. 具有大小限制,當超出上限時阻擋生產者塞入資料
  2. 當 Queue 沒有資料時,讓消費者暫停動作
  3. 支援關機動作,當生產者終止時,告知消費者可以收包包回家了

關於這個議題,推薦 MVP 安德魯這篇生產者 vs 消費者 - BlockQueue 實作,裡面對生產者消費者概念有更詳細的說明,其中還自己實作了滿足上述理想的 BlockQueue 物件。

有個好消息,.NET Framework 4 之後,.NET 加入了 BlockingCollection<T>,實作生產者消費者模式不需再花功夫自己寫,用專為生產者消費者設計的 BlockingQueue 即可輕鬆搞定。

BlockingCollection 提供以下功能:(參考:BlockingCollection Overview)

  • Add 及 Take 方法均為 Thread-Safe,可安心在多執行緒環境使用
  • 可設定容量上限
  • 沒有資料及超過上限時會自動 Block 程式,不需自己寫判斷,程式超好寫
  • 支援 TryAdd、TryTake,可不被 Block 或有 Block 時限進行資料存取
  • 支援 forech 過程移除元素 (使用 GetConsumingEnumerable,參考)
  • 可指定採用 Queue (先進先出) 或是 Stack (後進先出) 規則 參考
  • 可建立多個 BlockingCollection 組成陣列,使用 TryAddToAny、TryTakeFromAny 由陣列中存取資料 參考

下面來段範例程式感受一下它的好用。

詳情請前往原文出處 黑暗執行緒