线程池极端性能滞后

时间:2013-01-15 10:19:52

标签: c# .net multithreading performance threadpool

关于这个问题已经进行了不少讨论,但它们似乎无法解释我的特定问题。 线程使用ThreadPool而不是Thread类时,我遇到了严重的性能问题。

细节:

我已经构建了一个tcp服务器,当tcp服务器接受一个新客户端时,它会生成一个新线程来处理该客户端。所有相当简单,但它需要我的服务器太长时间来处理许多并发客户端。大约35个简单客户端只需发送2048字节缓冲区30秒,接收并关闭。

经过多次停顿后,我发现ThreadPool.QueueUserWorkItem最多需要26秒。我用它来产生新线程来处理新客户端。 用ThreadPool.QueueUserWorkItem替换new Thread()后,我的表现提高到不到一秒。

我很想解释为什么会这样。

澄清:

延迟与客户端代码无关,从调用ThreadPool.QueueUserWorkItem到启动clientMsgHandler.HandleIncomingMsgs 20秒即可通过。

延迟从第一个线程开始,实际上随着测试的继续而略有改善。我对解决方案不太感兴趣,对解释为什么会发生这种解决方案更感兴趣。 客户确实阻止了,但是很短的时间。

服务器代码:

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));

    }

4 个答案:

答案 0 :(得分:2)

阻止代码是ThreadPool的敌人。从您发布的示例中,无法确定阻止发生的位置,但我建议您查看代码路径以找出代码阻止的位置。在调试器中运行您的服务器,直到它开始显示高延迟,然后中断执行并查看VS的线程面板。这将显示线程已阻止的位置。很可能这是在同步IO上。考虑用异步代码替换。

答案 1 :(得分:1)

ThreadPool.QueueUserWorkItem - 对要执行的方法进行排队。该方法在线程池线程可用时执行。

  • ThreadPool等待空闲线程。
  • 当找到空闲线程时,ThreadPool使用它来执行您的方法。

为什么ThreadPool很慢?

  • 在ThreadPool中用完线程之前,上面两个并不是真正的原因。
  • 在.NET 3.5下,有2000个工作线程和1000个IO完成端口线程。 (4.0,4.5更多)。检查Jon Skeet's answer(线程池中的活动线程数)。

[例如]

.Net 2.0默认为每个可用处理器25个线程。这意味着如果排队30个任务,则最后五个必须等待线程在执行之前从池中可用。

解决方案:

SetMinThreads()以使最小线程数为30(对于.Net 2.0)。 这将提高性能,因为ThreadPool不会在需要时立即创建新线程;它只会在一定的时间间隔内完成。

注意:每个客户端使用一个线程不支持更多并发。

使用异步套接字 - 这些是非阻塞套接字,除了您不必轮询:只要发生“有趣”的事情,堆栈就会向程序发送一个特殊的窗口消息。

答案 2 :(得分:0)

在线程池中的前几个线程用完后,系统会在启动每个新线程池线程之前引入一个延迟(这不会影响重用线程)。

通过在启动任何线程之前将ThreadPool.SetMinThreads设置为适当大的值,可以更改线程池线程的初始数量。但你不支持这样做! (所以你没有听到我的意见......;)

你应该研究一种减少线程数量而不是这样做的方法。

答案 3 :(得分:0)

我认为这取决于你在clientMsgHandler.HandleIncomingMsgs()方法中做了什么。 线程池必须仅用于非常短的处理。

此外,线程池的每个可用处理器的默认大小为25个工作线程,请注意线程中的交叉锁定。

>> The Managed Thread Pool