如何确保 iocp 接收中的线程安全?
Posted
技术标签:
【中文标题】如何确保 iocp 接收中的线程安全?【英文标题】:How to ensure thread safe in iocp receive? 【发布时间】:2015-08-17 14:59:30 【问题描述】:DWORD bytes;
ULONG_PTR key; ChatOverlappedData* ol;
if (!GetQueuedCompletionStatus(hComp_, &bytes, &key, (LPOVERLAPPED*)&ol, 0))
return false;
int type = ol->getNetType();
if (type == net::kAction_Accept)
onAccept(ol, bytes, key);
else if (type == net::kAction_Recv)
onRecv(ol, bytes, key);
else if (type == net::kAction_Send)
return true;
考虑以下场景,
客户端alice
向服务器发送了两个命令,由三个数据包p1
p2
p3
组成。前两个包形成第一个命令c1
,第三个包形成第二个命令c2
。在onRecv
函数中,服务器需要将数据包推送到某种命令缓冲区中,形成完整的命令。
但是假设有三个线程t1
t2
t3
,每个线程从GetQueuedCompletionStatus
获取一个数据包(p1
,p2
,p3
),
由于 windows 是抢占式操作系统,线程 t2
可以在 t1
、t3
之前运行。结果命令缓冲区将为p2
->p1
->p3
或p2
->p3
->p1
。
如何保证将数据包推送到命令缓冲区的动作线程安全?
【问题讨论】:
【参考方案1】:最简单的解决方案是在每个方向上每个套接字只尝试一个重叠的 I/O 请求。所以发布一个重叠的读取操作,当它完成时,当你完成第一个处理时再发布一个。
发布多个这样的操作非常复杂,因为即使完成会按顺序发布,处理完成的线程也可能会乱序执行,您必须进行一些痛苦而复杂的跟踪。
针对同一连接在同一方向发布多个重叠操作的好处非常非常小。几乎永远不足以证明额外的复杂性是合理的。对于处理大量连接的服务器,通常根本不值得这样做,因为额外的内存消耗(或使用较小的缓冲区大小)实际上会使性能变差。
IOCP 的主要好处是更有效地发现哪些连接需要工作,并将该工作高效地分配给线程池。这就是最大连接数为 800 个的服务器与可以处理 10,000 个连接的服务器之间的区别。
【讨论】:
您对“我如何使用异步 I/O?” 的回答是 “改用同步 I/O。这样更容易。”?哎呀,这真的很有帮助。 不,一点也不。 I/O 仍然是异步的。该操作已发布并稍后完成。 嗯?假设您要处理 1,000 个客户。您发布了 1,000 个异步接收操作。假设其中两个向您发送数据。其中两个操作已完成,完成后将分派给您池中的两个线程。你还想要什么?你拿它比什么? 1000 个线程被同步操作阻塞? @amaninlove 你肯定想限制你积累的工作量,如果它变得太大,就会限制接收。一个简单的规则是在前一个请求完成之前不要为客户端发送新请求。这很容易通过使用指示客户端请求是否已发送的标志来实现。如果该标志设置为 true,则不要分派线程来执行新请求。当处理现有请求的线程完成它时,它可以将来自同一连接的任何“下一个请求”排队。 @amaninlove 虽然你可以做到,但我从不限制传入连接并始终从中接收数据。如果积累了太多未处理的数据,我会断开该客户端以“惩罚”它超出服务器。如果没有收到响应,这将转移客户端限制发送数据的义务。对于许多协议,这是合适的。一般来说,我相信将尽可能多的工作转移到客户端,以便服务器尽可能地扩展。当然,这并不总是合适的。以上是关于如何确保 iocp 接收中的线程安全?的主要内容,如果未能解决你的问题,请参考以下文章