簡體   English   中英

單一生產者和多個單線程消費者

[英]Single producer and multiple single-threaded consumers

我的應用程序從網絡接收數據包並將它們發送到一個或多個“處理器”。 (每個數據包屬於預定義的“流”,可以通過查看數據包數據來識別。)

目前有一個線程可以完成所有工作:

  1. 從網絡設備獲取數據包
  2. 識別每個數據包的處理器
  3. 將數據包分發到其處理器

以每秒2000萬個數據包的速率接收傳入數據(10個字節的60字節數據包)。

然而,該解決方案僅能夠跟上非常少量的流和處理器。 例如,在10個流的情況下,已經有大約10-20%的分組丟失。

由於步驟(3)是最昂貴的,我計划將該工作委托給工作線程池。

但是,我必須小心,因為處理器本身不是線程安全的。 因此,只有一個工作線程可以同時將數據包分派到同一個處理器。

這似乎是基於任務的編程的一個很好的用例。 但我無法輕易地將TBB文檔中解釋的設計模式與我的問題相匹配。

所以我的問題是:我如何組織我的消費者線程,以便他們將數據包均勻地分發到單線程處理器?

我不期待一個完全成熟的解決方案,但我會很高興你的建議或隨機的想法:)

我已經做了一些嵌入式編程,我不得不處理相對較高的吞吐量 - 沒有你在這里那么快! 希望你能使用比我習慣的硬件更強大的硬件......有一些簡單的策略應該適用於你的情況!

1.輸入/處理隊列和相關的內存管理至關重要。

如果您具有高數據速率,則傳入數據的隊列必須非常高效。 您應該盡可能少地進行處理,否則可能會丟失設備中的數據。 (我習慣於使用相對較小的緩沖區從某種快速串行設備讀取數據,因此實時約束設備可以在不丟失數據的情況下保留多長時間。這讓我養成了習慣處理從設備讀取作為一個完全獨立的任務,只處理讀取數據而沒有別的。)

一系列非常簡單的固定大小的預分配緩沖區就像它獲得的效率一樣高:擁有一個“空閑”緩沖區隊列和一個“填充”緩沖區隊列。 如果使用無鎖鏈接列表,則維護這些列表的速度非常快,並且許多操作系統中的入隊/出隊操作非常常見。

避免使用malloc或其他動態分配,因為當他們需要管理自己的“空閑”和“已分配”塊的數據結構時,它們具有顯着(通常是不可預測的)開銷。 如果它們在同一時間內釋放或分配內存,它們也可能執行可能無法預測地阻塞生產者或工作線程的鎖。 相反,嘗試找到較低級別的例程來分配和釋放操作系統為您的隊列提供的整個頁面(mix on unixy-platforms,VirtualAllocEx)。 這些通常必須做很少的工作,因為他們使用MMU功能來映射RAM的物理頁面,並且在內存中沒有復雜的數據結構來維護,在每次調用時具有更可靠的運行時,並且可以快速足以擴展您的免費列表,如果它的運行低。

在生產者中,不要擔心小於整個街區的單位。 從隊列中取出一個空閑塊,打包一個裝滿數據的塊,將其添加到要處理的隊列中。 如果您必須確保在固定的時間段內處理每個數據包,或者您需要處理“突發”數據速率,那么仍然嘗試從輸入設備讀取一個完整的緩沖區,但要么將塊的大小減小到是一個“合理的”時間,或使用超時並將部分填充的塊排入隊列並用某種空包“填充”剩余部分。 我發現這樣做通常比包含大量用於處理部分填充緩沖區的代碼更快。

如果可以,請非常仔細地設置生產者線程的處理器關聯和線程優先級。 理想情況下,您希望生產者線程具有比任何使用者線程更高的優先級,並且與特定核心綁定。 沒有什么可以阻止傳入數據在緩沖區空間不足的情況下被讀取。

2.處理

你說過有:

  1. 幾個流
  2. 幾個“處理器”,它們不是線程安全的

這里有用的是並行處理數據包上的處理器,但是從你的問題中可以清楚地知道這可能的程度。

處理器是否是跨線程的線程安全的? (只要它們在兩個不同的流上運行,我們可以在兩個不同的線程中運行處理器嗎?)

處理器是否在同一個流中的不同處理器之間是線程安全的? (我們可以在單獨的線程中在同一個流上運行多個處理器嗎?)

處理器是否需要按特定順序運行?

如果不知道這一點,仍然有一些通用的東西是有用的建議。

有一個第二個線程正在處理從生產者讀取完整緩沖區並將它們分派到適當的處理器(在其他線程中),然后將完整的緩沖區放回“空”隊列進行處理。 雖然你會失去一些直線效率(一個線程正在進行讀取和調度將比兩個稍微“快”),但至少這種方式不會阻止從輸入設備讀取,如果有一個瞬間鎖定。

創建或查找允許您將作業分配給線程池的庫,特別是如果您有多個處理器與可以並行運行的線程數相比。 實現某種類型的作業排隊相對簡單,允許作業之間的一些簡單關系(例如“這項工作需要先完成作業X和Y”,“此作業不能與任何其他使用的作業並行運行相同的處理器“)。 即使是一個簡單的策略,其中作業管理器只是在第一個可用線程上運行第一個可運行的作業,這可能非常有效。

盡量避免復制。 如果處理器可以“就地”處理數據包而不從緩沖區復制它們,那么您節省了許多無意義的周期。 即使你必須復制,讓幾個線程從'只讀'共享緩沖區復制數據比使用單個線程復制並將消息分派給多個線程更好。

如果檢查是否應該為給定數據包運行處理器的速度非常快,那么最好有幾個工作,每個工作都檢查它是否應該進行一些處理。 不是讓單個線程弄清楚哪些處理器應該在哪些數據包上運行,而是擁有多個線程(每個處理器或處理器組一個),檢查每個數據包一次是否應該運行處理器可能更快。 這可以歸結為這樣的想法:在多個線程中對多次只讀資源進行簡單檢查可能比在線程之間進行同步所花費的時間更少。

如果您可以並行運行處理器,如果它們正在處理來自不同流的數據,那么執行數據傳遞以獲取流的列表然后為每個流啟動作業是個好主意。 您還可以收集屬於每個流的數據包列表,但同樣,在一個作業檢查每個數據包的速度與在單個線程中收集該列表所需的時間之間進行權衡,並將每個數據包傳遞給每個數據包。他們各自的工作。

希望其中一些策略對您的情況有用! 讓我們知道它是如何工作的...這是你需要處理的大量數據,並且知道什么是有效的和沒有效率的數據速率比我習慣的更好! 祝好運!

這是我對可能的解決方案的想法。

假設我們有n個處理器。 我們來介紹n個互斥體,每個處理器一個。 我們還介紹一個數據包隊列。 所有傳入的數據包都放入此隊列。

工作線程的運行方式如下:

  1. 從傳入的數據包隊列中獲取數據包。
  2. 確定必要的處理器。
  3. 嘗試獲取相應的互斥鎖。 如果鎖定獲取成功,則處理該數據包。 否則,重新入隊並轉到1。
  4. 處理完成后,請轉到步驟1。

可能的缺點:

  1. 數據包重新排隊,這意味着它們可以被無序延遲/處理,這對您來說可能是一個交易破壞者(不確定)。
  2. 隊列上的爭用可能很高。 您可能希望考慮使用無鎖隊列。
  3. 隊列顯然會消耗額外的內存,我不知道你是否有備用內存。

編輯:更多關於內存消耗的想法 - 當然,它可以對隊列可以消耗的內存量設置上限 - 然后,問題是當內存不足時該怎么辦。 我會說最好的辦法就是開始丟棄數據包(我得到的印象是,在你的情況下丟棄一些並不是什么大問題),直到隊列耗盡。

與此有點相關 - 我認為這個用例的良好隊列實現應該不惜一切代價避免動態內存分配 - 預先分配內存並確保關鍵代碼路徑上沒有分配。

為什么不能使用多個隊列,每個處理器一個? 這些隊列可以無鎖(沒有互斥鎖)。

  1. 從網絡設備獲取數據包
  2. 識別每個數據包的處理器(PID)
  3. 將數據包推送到隊列[PID]
  4. 一個worker:來自隊列的進程數據包[K]

對於類似的問題,我使用無鎖環緩沖區的輪詢,自動覆蓋最舊的數據包。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM