【问题标题】:Tcp Server performance issues after implementing thread pool实现线程池后的 Tcp Server 性能问题
【发布时间】:2021-09-16 21:16:15
【问题描述】:

我已经阅读了几个关于这个问题的讨论并设法理解了一点,但在从 Thread 类转换为 ThreadPool 类时仍然存在一些解决性能问题的问题

详情:

我已经构建了一个 tcp 服务器,用于教育目的(任务不应该使用异步方法),它接受客户端连接并为每个客户端创建一个新线程。使用这种方法,应用程序的执行时间不到一秒,但是当决定转移到像线程池这样的下一级解决方案时,我的性能下降到仅 100 个客户端的 40-50 秒,我只发送一个 2048 字节的缓冲区,接收它并关闭。

前 12 个线程非常快,很可能是因为我的 CPU 是 6 核 12 线程,然后线程开始出现延迟。我对解决方案的想法和结构方法持开放态度。

服务器代码:

public void OnSocketReceive()
    {
        while (!this.exitServer)
        {
            if (!tcpListener.Pending())
            {
                Thread.Sleep(10);
                continue;
            }

            TcpClient client = tcpListener.AcceptTcpClient();

            IManageConnectedUser chatLogic = new ManageConnectedUser(connections, welcomeMessage);

            //Thread clientThread = new Thread(new ParameterizedThreadStart(chatLogic.OnClientConnection));
            //clientThread.Start(client);

            ThreadPool.QueueUserWorkItem(chatLogic.OnClientConnection, client);
        }

更多说明

到目前为止,通过我所做的调试和阅读的讨论,我得出的结论是,问题出在 OnClientConnection 函数中的阻塞代码,更具体地说是在 stringCreateHandler 的内部函数中。我在哪里收到此错误: System.Threading.ThreadInterruptedException: 线程从第 39 行的等待状态中断,即 Thread.Sleep(10);

public string stringCreateHandler(TcpClient client)
    {
        StringBuilder sb = new StringBuilder();
        try
        {
            do
            {
                if (client.Available > 0)
                {
                    while (client.Available > 0)
                    {
                        char ch = (char)client.GetStream().ReadByte();

                        if (ch == '\r')
                        {
                            continue;
                        }
                        if (ch == '\n')
                        {
                            return sb.ToString();
                        }

                        sb.Append(ch);
                    }
                }

                Thread.Sleep(10);
              
            } while (true);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }

    }

我已经检查了其余的代码,我认为没有更多的阻塞代码,但我给出了项目的链接https://github.com/nikolaymih/Chat-Project/tree/master/ChatProjectNewThreads 唯一的区别是 Thread 类而不是 ThreadPool 但这只是为了定位

【问题讨论】:

  • 使用异步方法,处理完成而不是轮询。使用await 可以轻松完成一些事情。说the task is not to use async methods 毫无意义。 Windows 是一个异步操作系统。所有 IO 都是异步的,阻塞操作是模拟的。实际的 IO 不使用线程池线程,它是在驱动程序级别异步执行的。应用程序级别的额外操作由不同的 IO 线程池执行。 Kestrel 速度如此之快,因为它不会阻塞。
  • “记录并重新抛出”模式没有错...记录通常比打印到控制台更微妙,但这是一个玩具应用程序
  • 你说the task is not to use async methods 但你也说I am open to solution ideas and structure approach 这两个东西是矛盾的。在现代编程中正确的做法是使用async,所以你需要解释为什么你不想使用它
  • @Enigmativity OP 重新抛出异常——它没有被吞没。
  • @Enigmativity throw e; 会丢失它,但 throw; 不会

标签: c# .net multithreading performance threadpool


【解决方案1】:

查看您的完整项目,我认为您的问题是 OnClientConnection 长期运行:在该连接关闭之前它不会返回,并且由于这是一个“聊天应用程序”,连接保持打开状态并且不是t 很快就关门了。

正如您所见,ThreadPool 以许多空闲线程开始,它会根据需要以大约每 500 毫秒一个的速率添加线程(尽管这是一个实现细节)。这通常不是问题:您不应该将 ThreadPool 用于非常长时间运行的任务,这些任务永远不会返回,并且永远占用线程。但是,由于您的 OnClientConnection 方法很长时间都不会返回,因此您只是在抓取和占用 ThreadPool 线程,并迫使 ThreadPool 继续扩展。

如果您想为每个客户端使用一个线程,最好为每个客户端创建一个新的长时间运行的线程。但是,这非常浪费:您将有大量线程挂在周围,但没有做太多事情。最好有少量线程不断处理来自大量客户端的消息。可以自己构建它,但使用 TcpListenerNetworkStream 的 async/await 功能要容易得多。

【讨论】:

  • 谢谢!我同意你所说的,特别是我在那个函数中永远持有一个线程。我希望在不改变整个结构和使用线程池的情况下做出决定,但现在我认为这是不可避免的。由于我的任务,我不喜欢使用 async/await,我将尝试自己构建它。如果您有任何有用的资源,我将不胜感激
  • @Nikolay 您可以尝试在程序开头添加这一行:ThreadPool.SetMinThreads(1000, 1000);。这样ThreadPool 将立即按需创建新线程,并且在达到 1,000 个线程(或您选择配置的任何数量)的阈值之前不会饱和。请注意,每个线程至少消耗 1 MB 的 RAM,因此在决定采用该路线之前,请确保您有大量可用的 RAM。
  • “长时间运行”的线程并不是特别浪费。如果它不经常运行,它加载的堆栈页面、TCB 等将被换出,它只会作为某个 OS 容器中的指针存在。
猜你喜欢
  • 2018-09-23
  • 2012-09-02
  • 1970-01-01
  • 2014-11-22
  • 2011-11-28
  • 1970-01-01
  • 1970-01-01
  • 2020-10-02
  • 1970-01-01
相关资源
最近更新 更多