【问题标题】:Thread pool extreme perfomance lag线程池极端性能滞后
【发布时间】:2013-01-15 10:19:52
【问题描述】:

关于这个问题已经进行了很多讨论,但它们似乎无法解释我的特定问题。 我在使用 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));

    }

【问题讨论】:

  • 尝试使用像Red Gate's ANTS Performance Profiler这样的性能分析器。
  • 你可能是线程池上的线程用完了吗?
  • 一般诊断是您的 HandleIncomingMsgs() 方法花费的时间太长。顺便说一句,在 finally 块中不使用 Monitor.Exit() 是一个严重的错误。
  • 为什么不将 TPL 的 Task.Factory.StartNew 方法与具有设置并发级别的自定义 TaskScheduler (msdn.microsoft.com/en-us/library/dd781658(v=vs.100).aspx) 一起使用?
  • Rudi,不,我正在使用功能强大的计算机,并且打开的线程少于 100 个。汉斯,这并不需要很长时间,实际上只需要不到一毫秒。但是感谢关于最后我会这样做的评论,以防万一。麦克斯,我去看看。

标签: c# .net multithreading performance threadpool


【解决方案1】:

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

【讨论】:

  • 你可能是对的。我试过了,阻塞线程在线程池中运行时会导致大量延迟。你能解释一下吗?
【解决方案2】:

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

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

为什么线程池很慢?

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

[例如]

.Net 2.0 默认每个可用处理器有 25 个线程。这意味着如果您将 30 个任务排队,最后 5 个任务必须等待线程从池中可用,然后才能执行。

解决方案:

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

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

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

【讨论】:

  • 我的进程在四核 cpu 上最多有 50 个线程,没有加起来。
  • 显然,没有线程稀缺的机会 :) 你可以看看应该使用哪种 I/O 策略?阻塞或非阻塞 I/O(你的 HandleIncomingMsgs 是什么)
【解决方案3】:

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

您可以通过在启动任何线程之前将ThreadPool.SetMinThreads 设置为适当大的值来更改线程池线程的初始数量。但你不应该那样做! (所以你没有从我这里听到……;)

您应该寻找一种减少线程数量的方法,而不是这样做。

【讨论】:

  • 轰隆隆。您的服务器刚刚爆炸。
  • 关闭。最后我听说这是一个 0.5 秒的延迟,在服务器应用程序中你可以摆弄 MinThreads(一点点)
  • 没错。正确的方法是更改​​实现,使其不使用太多线程。该答案的范围超出了我刚才必须回复的时间!
【解决方案4】:

我认为这取决于您在 clientMsgHandler.HandleIncomingMsgs() 方法中所做的事情。 线程池只能用于非常短的处理。

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

>> The Managed Thread Pool

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-22
    • 2023-04-04
    • 1970-01-01
    • 2021-09-16
    • 1970-01-01
    • 2012-09-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多