Delphi XE4 + Indy TCP服务器:大量线程

Posted

技术标签:

【中文标题】Delphi XE4 + Indy TCP服务器:大量线程【英文标题】:Delphi XE4 + Indy TCP server : large number of threads 【发布时间】:2021-11-22 21:11:15 【问题描述】:

Windows 服务有多个永久 TCP 连接,接受数据,在单独的线程中处理它,定期向所有连接的客户端发送更新。处理必须尽可能实时完成,客户端每 2-4 秒发送一条消息。

当使用 1900 个连接时,服务大约有 1970 个线程。到现在还好。但是当线程总数超过 2000-2200 时,事情就变得奇怪了——有时处理线程会无缘无故地阻塞 10 秒,即使使用实时优先级也是如此。在 10 秒内,传入队列会填满数千个请求,因此即使是一秒钟的延迟也是不可接受的。尝试使用 TSchedulerOfThread 来限制线程数,但实际上这只限制了连接数...

我知道 Indy 每个 TCP 连接消耗一个线程。所以我测试了使用 Windows 消息传递的 Overbyte ICS(不完全是这个的粉丝,但没有真正的选择)。线程数显着降低(仍然是 70,但没关系),在 3000 个连接时也能正常工作。几个小时后,服务在没有调用堆栈的情况下随机崩溃(正在使用 madExcept)。 Windows 事件日志中只有一条记录标记为“应用程序崩溃”。

因此,Indy 或 ICS 都不适合我。

中间有什么解决办法吗?我可以负担得起使用 1000 个线程,但不是 3000...

【问题讨论】:

您的应用是 32 位还是 64 位?因为在 32 位上,2000 个线程将消耗几乎全部可用内存,仅用于堆栈的 32 位进程。 对不起,忘了说。它是 64 位的。 AFAIK 对每个进程的线程数没有任何硬性限制,尤其是在 64 位上。因此,您在系统上看到的性能影响很可能是基于其他系统资源的某些限制,可能是可用物理内存、内核数量或其他繁重的进程。 目前该服务在 Intel Xeon Gold 5218、64 核、128 GB 内存上运行。服务工作集是 230 MB,所以硬件应该不是问题。 CPU 的总体利用率低于 20%。奇怪的是,即使有 2200 个线程,它也可以完美运行几天而没有问题,或者每隔几个小时就会出现应用程序挂起。没找到规则... 2000 个消息“生产者”,只有一个“工作”线程用于繁重的工作,嗯,我认为拥有多个“工作”线程会更好更可靠,每个线程都会从其中获取消息排队并完成工作。在这种情况下,当某个工作线程由于某种原因冻结时,您的工作进程不会停止(除非该问题跨越所有“工作”线程)。 【参考方案1】:

根据 cmets 中的其他信息,问题很可能来自您的工作线程的 Sleep(),由 Sleep() 的文档提供:

经过休眠间隔后,线程准备好运行。如果您指定 0 毫秒,线程将放弃其时间片的剩余部分,但仍保持就绪状态。请注意,就绪线程不能保证立即运行。

我认为 Windows 中的线程调度程序不能很好地处理这么多线程,而且很可能在您的Sleep(5) 之后,Windows 仍然忙于在其他线程之间切换。此外,如果您每秒收到 1000 条消息,那么即使是 5 毫秒的睡眠时间也是很长的。

在这种情况下,我们总是使用TThreadedQueue(不确定您的Delphi 版本是否可用)和PushItem()/PopItem() 用于生产者/消费者的数据传输。你不需要让你的线程休眠,因为“PopItem()waits for a new message to arrive in the queue (it uses internal logic with aTMonitor`同步)。我们的工作线程看起来像这样:

while not Terminated do
begin
  FMsg := MsgQueue.PopItem;  //ThreadQueue waits (for PopTimeout) here for item to appears
  if Assigned(FMsg) then
  begin
    AddLogDebug('Begin DoProcessMsg');
    DoProcessMsg;
    AddLogDebug('End DoProcessMsg');
  end;
end;

【讨论】:

在单次运行中,工作线程从传入队列(指向数据结构的指针的 TList)中获取所有消息,并一一处理,没有任何延迟。我犯了错误,如果队列上没有消息,线程正在休眠 1 毫秒。但可以肯定的是,绝对值得用 TTreadQueue 进行测试并避免睡眠。感谢您的提示。 @Dusan Btw,关于您按接收请求的顺序处理消息的要求 - 仍然可以选择使用许多工作线程,因为在同步队列(如 TThreadedQueue)中,可以保证消息会出来从队列中按他们进入的顺序,但在并行工作人员中,可以先处理后面的项目,但处理的开始将按照确切的顺序,这可以用来满足您的要求。 不幸的是,多个工作线程是不可能的......因为下一次计算的结果取决于之前的计算结果,并且不可能进行并行处理。与此同时,我更新了 Overbyte ICS 并在周末进行了测试,现在看起来好多了。唯一让我担心的是内存泄漏的可能性。但首先我必须排除我的错误

以上是关于Delphi XE4 + Indy TCP服务器:大量线程的主要内容,如果未能解决你的问题,请参考以下文章

【delphi】请问,如果只发送一个心跳包的话,用indy的tcp好还是udp好?

Indy10 Tcp接收数据问题

delphi 7 下安装 indy 10.5.8 教程

delphi2009(10,xe)下indy10发送utf8字符串

Delphi XE 的 SOAP 服务器和客户端应用程序 VCL+indy 演示?

Delphi、Indy TLS 1.2 和 TLS 1.3 开始状态码 403