multithreading - Delphi XE4 + Indy TCP服务器:大量线程
问题描述
Windows 服务有多个永久 TCP 连接,接受数据,在单独的线程中处理它,定期向所有连接的客户端发送更新。处理必须尽可能实时,客户端每 2-4 秒发送一条消息。
当使用 1900 个连接时,该服务有大约 1970 个线程。到现在还好。但是当线程总数超过 2000-2200 时,事情就变得奇怪了——有时处理线程会无缘无故地阻塞 10 秒,即使使用实时优先级也是如此。在 10 秒内,传入队列会填满数千个请求,因此即使是一秒钟的延迟也是不可接受的。尝试使用 TSchedulerOfThread 来限制线程数,但实际上这只限制了连接数......
我知道 Indy 每个 TCP 连接消耗一个线程。所以我测试了使用 Windows 消息传递的 Overbyte ICS(不完全是这个的粉丝,但没有真正的选择)。线程数显着降低(仍然是 70,但没关系),在 3000 个连接时也能正常工作。几个小时后,服务在没有调用堆栈的情况下随机崩溃(正在使用 madExcept)。Windows 事件日志中只有一条记录标记为“应用程序崩溃”。
所以 Indy 和 ICS 都不适合我。
中间有什么解决办法吗?我可以负担得起使用 1000 个线程,但不是 3000...
解决方案
根据评论中的附加信息,问题很可能来自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 a
TMonitor`同步)。我们的工作线程看起来像这样:
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;