2013-01-15 57 views
3

關於這個問題已經有不少討論,但他們似乎無法解釋我的特殊問題。 線程使用ThreadPool而不是Thread類時,我遇到了嚴重的性能問題。線程池極端性能滯後

細節:

我建立了一個TCP服務器,當TCP服務器接受新的客戶端就產生一個新的線程來處理該客戶端。所有相當簡單,但它需要我的服務器方式太長,以處理許多併發的客戶端。約30個簡單的客戶端只需發送一個2048字節的緩衝區30秒,收到並關閉。

經過很多秒錶,我發現ThreadPool.QueueUserWorkItem需要長達26秒。我用它來產生處理新客戶端的新線程。 將ThreadPool.QueueUserWorkItem替換爲new Thread()後,我的表現改善至不到一秒。

我很想解釋爲什麼會發生這種情況。

澄清:

延遲無關與客戶端的代碼,從目前直到clientMsgHandler.HandleIncomingMsgs開始20秒可以通過ThreadPool.QueueUserWorkItem被調用。

滯後從第一個線程開始,實際上隨着測試的繼續而略微提高。我對解決方案不太感興趣,並且更關心解釋爲什麼會發生這種情況。 客戶端確實阻塞,但時間很短。

服務器代碼:

private void AddTcpClientMsgHandler(TcpClient tcpClient) 
    { 
     //lock so no addition of client and closure can occur concurrently 
     Stopwatch watch = new Stopwatch(); 
     watch.Start(); 
     Monitor.Enter(this); 
     int pWatchIdx = watchIDX++; 
     if (!isOpen) 
      throw new ObjectDisposedException(ResourceAlreadyClosed); 

     TcpClientMsgHandler clientMsgHandler = CreateClientHandler(tcpClient);           
     clientMsgHandlerManager.AddTcpClientMsgHandler(clientMsgHandler); 
     //ThreadPool.QueueUserWorkItem(clientMsgHandler.HandleIncomingMsgs); takes 20 seconds to run 
     Thread thread = new Thread(clientMsgHandler.HandleIncomingMsgs); 
     thread.Start(); 
     watch.Stop(); 
     Monitor.Exit(this); 
     Console.WriteLine(string.Format("Iteration {0} took {1} Client {2}", pWatchIdx.ToString(),watch.Elapsed, tcpClient.Client.RemoteEndPoint)); 

    } 
+0

嘗試使用性能分析器,例如[Red Gate的ANTS性能分析器](http://www.red-gate.com/products/dotnet-development/ants-performance-profiler/)。 –

+0

你可能已經沒有線程池中的線程了嗎? –

+2

通用診斷是您的HandleIncomingMsgs()方法過長。在finally塊中不使用Monitor.Exit()是嚴重的bug。 –

回答

2

阻止代碼是ThreadPool的敵人。從您發佈的示例中,無法確定阻塞的發生位置,但我建議您查看代碼路徑以找出代碼阻塞的位置。在調試器中運行服務器,直到它開始顯示高延遲,然後中斷執行並查看VS的線程面板。這會告訴你線程被阻塞的地方。很可能這是在同步IO上。考慮用異步代碼替換。

+0

你可能是對的。我嘗試過,並且在ThreadPool中運行時阻塞線程會導致大量延遲。你能解釋一下嗎? – lavuy

0

在線程池的前幾個線程後用完,系統地介紹起每一個新的線程池線程之前的延遲(這並不影響再次使用的線程)。

在啓動任何線程之前,您可以通過將ThreadPool.SetMinThreads設置爲合適的較大值來更改線程池線程的初始數量。但你沒有支持這麼做! (所以你沒有聽到我的話......)

你應該研究一種減少線程數量的方法,而不是這樣做。

+0

繁榮。你的服務器爆炸了。 – spender

+0

關閉。最後我聽說這是一個0.5秒的延遲,並在服務器應用程序中,你可以提供MinThreads的機智(一點) –

+0

正是。正確的方法是更改​​實現,以便它不使用太多的線程。答案的範圍超出了我現在必須回答的時間! –

0

我認爲這取決於您在clientMsgHandler.HandleIncomingMsgs()方法中的做法。 線程池只能用於非常短的處理。

此外,線程池的默認大小爲每個可用處理器25個工作線程,請注意線程中的交叉鎖定。

>> The Managed Thread Pool

1

ThreadPool。QueueUserWorkItem - 排隊執行的方法。該方法在線程池線程變爲可用時執行。

  • 線程池等待空閒線程。
  • 當找到空閒線程時,ThreadPool使用它來執行你的方法。

爲什麼ThreadPool很慢?

  • 以上兩個不是真正的理由,直到您用完ThreadPool中的線程。
  • 在.NET 3.5下,有2000個工作線程和1000個IO完成端口線程。 (甚至在4.0,4.5中更多)。檢查Jon Skeet's答案(線程池中的活動線程號)。

[供例如]

NET 2.0默認爲每個可用處理器25個線程。這意味着如果您排隊完成30個任務,則最後五個任務必須等待線程在執行前從池中變爲可用。

解決方案:

SetMinThreads()使螺紋30(用於.NET 2.0)的最小數目。 這將提高性能,因爲ThreadPool在需要時不會立即創建新線程;它只能在一定的時間間隔內完成。

注意:每個客戶端使用一個線程不支持更多的併發性。

使用異步套接字 - 這些都是非阻塞套接字,不同之處在於您不必輪詢:只要發生「有趣」的事情,堆棧就會向程序發送一個特殊的窗口消息。

+0

我的進程在四核CPU上至多有50個線程,不會加起來。 – lavuy

+0

你正在使用哪個.Net框架? –

+0

我正在使用.Net Framework 4.5 – lavuy