c - WSAWaitForMultipleObjects 阻塞任何线程但最后一个
Posted
技术标签:
【中文标题】c - WSAWaitForMultipleObjects 阻塞任何线程但最后一个【英文标题】:c - WSAWaitForMultipleObjects blocking any thread but last 【发布时间】:2011-08-23 22:32:13 【问题描述】:我的多线程 SMTP/POP3 服务器有问题。服务器启动一个线程池来处理传入的连接。主线程创建套接字和线程,以适当的结构将套接字作为参数传递。线程的循环函数如下:
SOCKET SMTP_ListenSocket = (SOCKET) data->SMTPconn;
SOCKET POP3_ListenSocket = (SOCKET) data->POP3conn;
static struct sockaddr_in ClntAddr;
unsigned int clntLen = sizeof(ClntAddr);
hEvents[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
hEvents[1] = CreateEvent(NULL, FALSE, FALSE, NULL);
hEvents[2] = exitEvent; //HANDLE FOR A MANUAL RESET EVENT
WSAEventSelect(SMTP_ListenSocket, hEvents[0], FD_ACCEPT);
WSAEventSelect(POP3_ListenSocket, hEvents[1], FD_ACCEPT);
while(1)
DWORD res = WaitForMultipleObjects(3, hEvents, FALSE, INFINITE);
switch(res)
case WAIT_OBJECT_0:
ClientSocket = my_accept(SMTP_ListenSocket,(struct sockaddr *) &ClntAddr,&clntLen);
/* ... */
my_shutdown(ClientSocket,2);
my_closesocket(ClientSocket);
ClientSocket = INVALID_SOCKET;
break;
case WAIT_OBJECT_0 + 1:
ClientSocket = my_accept(POP3_ListenSocket,(struct sockaddr *) &ClntAddr,&clntLen);
/* ... */
my_shutdown(ClientSocket,2);
my_closesocket(ClientSocket);
ClientSocket = INVALID_SOCKET;
break;
case WAIT_OBJECT_0 + 2:
exitHandler(0);
break;
//end switch
//end while
当池只包含一个线程时没有问题。当池包含多个线程时,只有一个线程接受传入的连接
【问题讨论】:
不是这样的答案,但您是否考虑过使用 IO 完成端口? msdn.microsoft.com/en-us/library/aa365198(v=vs.85).aspx 【参考方案1】:您的池线程是否都调用了相同的代码?如果是这样,那么不要像这样使用WaitForMultipleObjects()
(或WSAWaitForMultipleEvents()
)。这种模型只有在一个线程轮询连接时才能可靠地工作。如果您有多个线程同时轮询,那么您就有竞争条件。
相反,您应该将AcceptEx()
与重叠的 I/O 或完成端口一起使用。创建套接字的线程可以在每个套接字上调用AcceptEx()
来对每个套接字进行新的操作排队,然后池化线程可以使用GetQueuedCompletionStatus()
或GetOverlappedResult()
将挂起的连接出列,而不必担心踩踏其他线程。一旦连接被接受,接收线程可以根据需要对其进行处理,然后调用AcceptEx()
为该套接字排队一个新操作。
【讨论】:
好的,但是主线程(创建套接字)不能等待传入的连接。 AcceptEx 函数阻塞了主线程,这不是我们想要的行为。它必须创建套接字,而不是线程池并等待控制台事件。只有池中的线程必须等待传入连接。你有一些 io-completion 教程的链接吗?我还没有发现任何有用的东西AcceptEx()
不会阻塞调用线程,如果您对套接字使用重叠 I/O 或完成端口。完成端口可以更好地扩展,因此如果您需要接受大量连接,则应该使用它。看看CreateIoCompletionPort()
和GetQueuedCompletionStatus()
。【参考方案2】:
这里的每个线程都在设置一个新的WSAEventSelect
之前进入等待。这会覆盖任何现有的事件选择。这意味着,一旦一个线程(称为线程 A)接受一个连接,就没有与套接字关联的事件。
要解决这个问题,您应该在您的交换机内再次调用WSAEventSelect
,紧跟在accept()
之后。这将在进入任何可能冗长的处理之前立即恢复事件绑定。
请注意,如果时间恰到好处,两个线程可能会因同一事件而被唤醒。如果接受失败,您可以通过返回等待循环来解决这个问题,但这有点不令人满意。
因此,与其滚动您自己的版本,不如在此处使用IO completion ports。 I/O 完成端口具有许多附加功能,并且可以避免两个线程可能接收相同事件的潜在竞争条件。当您的代码不受 CPU 限制时,他们还会采取措施减少上下文切换。
【讨论】:
调用 WSAEventSelect 没有解决问题。你能给我一些关于 IO 完成的提示吗?教程、代码示例以上是关于c - WSAWaitForMultipleObjects 阻塞任何线程但最后一个的主要内容,如果未能解决你的问题,请参考以下文章