簡體   English   中英

更多線程,更好的性能?

[英]More threads, better performance?

當我寫一個消息驅動的應用程序。 就像標准的Windows應用程序一樣,它廣泛使用消息傳遞進行內部操作,這對於線程化的最佳方法是什么?

在我看來,基本上有三種方法(如果你有任何其他設置,請分享):

  1. 有一個線程處理所有消息。
  2. 為不同的消息類型(通用,UI,網絡等)提供單獨的線程
  3. 擁有多個共享和處理單個消息隊列的線程。

那么,三者之間是否存在顯着的性能差異? 以下是一些一般性的想法:顯然,最后兩個選項受益於多個處理器的情況。 另外,如果任何線程正在等待外部事件,則其他線程仍然可以處理不相關的消息。 但忽略這一點,似乎多線程只增加開銷(線程切換,更不用說更復雜的同步情況)。

另一個問題:您是否建議在標准的Windows消息傳遞系統上實現這樣的系統,或者實現單獨的隊列機制,為什么?

線程模型的具體選擇應該由您嘗試解決的問題的性質驅動。 設計這種應用程序的線程模型不一定有一種“正確”的方法。 但是,如果我們采用以下假設:

  1. 消息頻繁到達
  2. 消息是獨立的,不會過分依賴共享資源
  3. 希望盡可能快地響應到達的消息
  4. 您希望應用程序在處理體系結構(即多碼/多CPU系統)之間很好地擴展
  5. 可擴展性是關鍵的設計要求(例如,以更快的速度傳遞更多消息)
  6. 對線程故障/長操作的彈性是可取的

根據我的經驗,最有效的線程架構是使用線程池。 所有消息都到達單個隊列,多個線程在隊列上等待並在消息到達時處理消息。 線程池實現可以為您擁有的所有三個線程分發示例建模。

#1單線程處理所有消息=>線程池只有一個線程

#N線程每N個消息類型=>具有N個線程的線程池,每個線程都會查看隊列以查找適當的消息類型

#3所有消息的多個線程=>具有多個線程的線程池

此設計的好處是,您可以按照處理環境或消息負載的比例縮放線程中的線程數。 線程數甚至可以在運行時擴展以適應所體驗的實時消息負載。

大多數平台都有許多好的線程池庫,包括.NET,C ++ / STL,Java等。

至於你的第二個問題,是否使用標准的Windows消息調度機制。 這種機制帶來了巨大的開銷,實際上只用於通過Windows應用程序的UI循環來傳送消息。 除非這是您要解決的問題,否則我建議不要將其用作一般的消息調度解決方案。 此外,Windows消息攜帶的數據非常少 - 它不是基於對象的模型。 每個Windows消息都有一個代碼和一個32位參數。 這可能不足以基於干凈的消息傳遞模型。 最后,Windows消息隊列不是設計用於處理隊列飽和,線程飢餓或消息重新排隊等情況; 這些是在實施體面的消息排隊解決方案時經常出現的情況。

如果不知道工作量(即事件的統計分布,一般情況下),我們無法確定

  • 具有多個服務器的單個隊列至少同樣快,並且通常更快,因此1,3優於2。
  • 大多數語言中的多個線程增加了復雜性,因為需要避免爭用和多寫者問題
  • 持續時間長的進程可以阻止處理其他可以更快完成的事情。

所以馬背猜測,擁有一個事件隊列,有幾個服務器線程從隊列中取出事件, 可能會快一點。

確保為隊列使用線程安全的數據結構。

這完全取決於。

例如:

  • GUI隊列中的事件最好由單個線程完成,因為事件中存在隱含順序,因此它們需要串行完成。 這就是為什么大多數GUI應用程序都有一個線程來處理事件,盡管可能有多個事件來創建它們(並且它不會阻止事件線程創建一個作業並將其處理到一個工作池(見下文))。

  • 套接字上的事件可能是並行完成的(假設是HTTP),因為每個請求都是無狀態的,因此可以獨立完成(好吧我知道這已經過度簡化了HTTP)。

  • 工作崗位每個工作都是獨立的並且排在隊列中。 這是使用一組工作線程的經典案例。 每個線程獨立於其他線程執行可能很長的操作。 完成后返回隊列進行另一項工作。

一般來說,不要擔心線程的開銷。 如果你只談論其中的一小部分,那就不會成為一個問題。 競爭條件,死鎖和爭用是一個更大的問題,如果你不知道我在說什么,你在解決這個問題之前需要做很多閱讀。

我會使用選項3,使用我選擇的語言提供的任何抽象。

請注意,有兩個不同的性能目標,您尚未說明您的目標:吞吐量和響應能力。

如果您正在編寫GUI應用程序,則UI需要具有響應能力。 您不關心每秒可以處理多少次點擊,但您確實關心的是在十分之一秒內顯示一些響應(理想情況下更少)。 這是最好有一個專門用於處理GUI的線程的原因之一(在其他答案中已經提到了其他原因)。 GUI線程基本上需要將Windows消息轉換為工作項,並讓您的工作隊列處理繁重的工作。 完成工作后,它會通知GUI線程,然后GUI線程會更新顯示以反映任何更改。 它可以像繪制窗口一樣,但不會渲染要顯示的數據。 這為應用程序提供了快速的“快照”,這是大多數用戶在談論性能時所需要的。 他們不關心是否需要15秒才能做一些艱難的事情,只要他們點擊按鈕或菜單,就會立即做出反應。

另一個性能特征是吞吐量。 這是您可以在特定時間內處理的作業數。 通常,這種類型的性能調整僅在服務器類型應用程序或其他重型處理上需要。 這可以衡量一小時內可以提供多少個網頁,或者渲染DVD需要多長時間。 對於這些類型的作業,您希望每個CPU有1個活動線程。 少於此,你將浪費空閑的時鍾周期。 更重要的是,線程將爭奪CPU時間並相互絆倒。 請查看本文中DDJ文章中的第二個圖表,以了解您正在處理的權衡問題。 請注意,由於阻塞和鎖定等原因,理想的線程數高於可用CPU的數量。 關鍵是活動線程的數量。

我有一個服務於消息隊列的線程池,並且可以輕松配置池中的線程數(甚至可以在運行時)。 然后用預期的負載測試它。

這樣您就可以看到實際的相關性 - 如果您的初始假設發生變化,您可以輕松地改變您的方法。

更復雜的方法是讓系統反省自己的性能特征,並調整資源的使用,特別是線程的使用。 對於大多數自定義應用程序代碼來說可能有點過分,但我確信有些產品可以做到這一點。

至於windows事件問題 - 我認為這可能是一個特定於應用程序的問題,在一般情況下沒有正確或錯誤的答案。 也就是說,我通常會實現自己的隊列,因為我可以根據手頭任務的具體特點進行定制。 有時可能涉及通過Windows消息隊列路由事件。

一個好的開始就是問問自己為什么需要多個線程。

經過深思熟慮的這個問題的答案將引導您對后續問題的最佳答案,“我應該如何在我的應用程序中使用多個線程?”

那一定是隨后的問題; 不是主要問題。 第一個問題必須是原因,而不是如何。

我認為這取決於每個線程將運行多長時間。 每條消息都需要相同的時間來處理嗎? 或者某些消息會花費幾秒鍾。 如果我知道消息A需要10秒鍾才能完成,我肯定會使用一個新線程,因為為什么我要為長時間運行的線程保留隊列...

我的2美分。

我認為選項2是最好的。 讓每個線程執行獨立任務將為您提供最佳結果。 如果多個線程正在執行某些I / O操作(如磁盤讀取,讀取公共套接字等),則第3種方法可能會導致更多延遲。

是否使用Windows消息傳遞框架處理請求取決於每個線程可能具有的工作負載。 我認為窗戶限制了沒有。 可以排隊的消息最多為10000.對於大多數情況,這應該不是問題。 但是如果你要排隊很多消息,這可能是需要考慮的事情。

單獨的隊列可以提供更好的控制,您可以按照自己的方式對其進行重新排序(可能取決於優先級)

是的,您的選擇之間會有性能差異。

(1)介紹了用於消息處理的瓶頸
(3)引入了鎖爭用,因為您需要同步對共享隊列的訪問。

(2)開始向正確的方向發展......盡管每種消息類型的隊列都有點極端。 我可能建議從應用程序中的每個模型的隊列開始,並在其中添加隊列,以便提高性能。

如果你喜歡選項#2,聽起來你會對實現SEDA架構感興趣。 這將需要一些閱讀來了解正在發生的事情,但我認為該架構非常符合您的思路。

BTW, Yield是一個很好的C ++ / Python混合實現。

暫無
暫無

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

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