[英]multi-thread c# application hitting high CPU usage
我正在開發將連接到x個硬件設備的應用程序。 我對ReaderLayer進行了設計,以便有一個專用線程,代碼在該線程中運行以連接到單個設備並連續讀取設備數據緩沖區。 閱讀器層的代碼如下:
while (true)
{
// read buffer from the reader
IList<IRampActiveTag> rampTagList = ConnectedReader.ReadBuffer();
if (rampTagList != null && rampTagList.Any())
{
// trigger read event handler
if (ReadMessage != null)
ReadMessage(this, new RampTagReadEventArg(rampTagList));
}
}
在閱讀器層之上構建了一個邏輯層,該邏輯層負責處理從閱讀器接收的數據並通過HTTP Post轉發。 它有y個線程,每個線程運行一個單獨的邏輯塊,該邏輯塊必須處理要寫入其線程安全隊列的相關信息。 邏輯層訂閱Reader層公開的ReadForward
事件,並通過寫入其ConcurrentQueue
將數據轉發到相關邏輯塊。
每個邏輯塊中的代碼都非常簡單:
public void ProcessLogicBuffer()
{
while (true)
{
// Dequeue the list
IRampActiveTag tag;
LogicBuffer.TryDequeue(out tag);
if (tag != null)
{
ProcessNewTagReceivedLogic(tag);
Console.WriteLine("Process Buffer #Tag {0}. Buffer Count #{1}", tag.NewLoopId, LogicBuffer.Count);
}
}
}
對於Reader層和Logic層while(true)
,循環的布局幾乎相同。 但是,當我使用3個讀取器和3個邏輯塊進行測試時,我發現我的CPU利用率躍升到77%。 我迅速將CPU使用范圍縮小到邏輯線程,因為我收到2個塊的50%的使用率和1個塊的25%的使用率。
我只需在3個塊的邏輯線程中添加Thread.Sleep(100)就能將CPU使用率降低到3%左右,但是,我擔心我的邏輯可能不同步。 通過查看樣本,任何人都可以向我提出任何改進建議,因為生產代碼將需要使用大約30個邏輯塊。 我需要改變架構嗎?
您在此循環中進行了很多不必要的輪詢:
public void ProcessLogicBuffer()
{
while (true)
{
// Dequeue the list
IRampActiveTag tag;
LogicBuffer.TryDequeue(out tag);
if (tag != null)
{
ProcessNewTagReceivedLogic(tag);
Console.WriteLine("Process Buffer #Tag {0}. Buffer Count #{1}", tag.NewLoopId, LogicBuffer.Count);
}
}
}
假設大多數時間隊列是空的,那么此代碼所做的大部分工作都是重復檢查隊列。 “我有東西嗎?現在怎么樣?現在呢?……”
您可以通過使用BlockingCollection替換ConcurrentQueue
來擺脫所有無用的輪詢。 假設您將LogicBuffer
更改為BlockingCollection
,則循環如下所示:
public void ProcessLogicBuffer()
{
foreach (var tag in LogicBuffer.GetConsumingEnumerable())
{
ProcessNewTagReceivedLogic(tag);
Console.WriteLine("Process Buffer #Tag {0}. Buffer Count #{1}", tag.NewLoopId, LogicBuffer.Count);
}
}
GetConsumingEnumerable會在項目到達時使它們出隊,並將繼續這樣做,直到集合為空並被標記為已添加完成。 請參見BlockingCollection.CompleteAdding 。 但是,真正的GetConsumingEnumerable
是GetConsumingEnumerable
了不忙的等待。 如果該集合為空,則它等待項可用的通知。 它不會對TryDequeue
進行很多無用的輪詢。
更改使用ConcurrentQueue
代碼,使其改為使用BlockingCollection
非常容易。 您大概可以在一個小時內完成。 這樣做將使您的代碼簡單得多,並且將消除不必要的輪詢。
如果需要進行一些定期處理,則有兩種選擇。 如果您希望在讀取BlockingCollection
的同一循環中完成此操作,則可以將使用GetConsumingEnumerable
的循環更改為:
Stopwatch sw = Stopwatch.StartNew();
TimeSpan lastProcessTime = TimeSpan.Zero;
while (true)
{
IRampActiveTag tag;
// wait up to 200 ms to dequeue an item.
if (LogicBuffer.TryTake(out tag, 200))
{
// process here
}
// see if it's been 200 ms or more
if ((sw.ElapsedMilliseconds - lastProcessTime.TotalMilliseconds) > 200)
{
// do periodic processing
lastProcessTime = sw.Elapsed;
}
}
這將為您提供200到400毫秒范圍內的定期處理速率。 在我看來,這有點丑陋,可能無法滿足您的目的。 您可以將超時從20毫秒減少到20毫秒,而不是200毫秒,這將使您更接近200毫秒的輪詢速率,而對TryTake
調用的代價是10倍。 您可能不會注意到CPU使用率的差異。
我的偏好是將定期處理循環與隊列使用者分開。 創建一個計時器,每200毫秒觸發一次,並使其工作。 例如:
public void ProcessLogicBuffer()
{
var timer = new System.Threading.Timer(MyTimerProc, null, 200, 200);
// queue processing stuff here
// Just to make sure that the timer isn't garbage collected. . .
GC.KeepAlive(timer);
}
private void MyTimerProc(object state)
{
// do periodic processing here
}
這將使您的更新頻率非常接近200毫秒。 計時器proc將在單獨的線程上執行,為true,但是該線程處於活動狀態的唯一時間將是在計時器觸發時。
確實很簡單,您永遠不會屈服於控制,因此您的代碼始終在執行,因此代碼的每個線程都使用100%的1核(如果使用4核,則使用25%的cpu)。
那里沒有魔術,Windows不會“試圖猜測”您想要等待並限制您,您需要明確等待,您需要顯示ProcessNewTagReceivedLogic的實現,但我猜測您正在“某個地方”網絡連接,而不是輪詢(只需檢查一次,直到得到非null的值),而是要調用實際上持有並產生直到得到答案的東西。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.