c - WSAWaitForMultipleObjects 阻塞除最后一个线程之外的任何线程
我的多线程 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
当池中仅包含一个线程时,没有问题。当池包含更多线程时,只有一个线程接受传入连接
i have a problem with a multi-thread SMTP/POP3 server. The server starts a pool of threads to handle incoming connections. The main thread create the sockets and the the threads, passing the sockets as parameters in a proper structure. The loop function for the threads is the following:
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
When the pool contains only one thread there's no problem. When the pool consist of more threads, only one thread accepts the incoming connections
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您是否有池线程都调用相同的代码?如果是这样,那么不要像这样使用
WaitForMultipleObjects()
(或WSAWaitForMultipleEvents()
)。这种模型只有在一个线程轮询连接时才能可靠地工作。如果您有多个线程同时轮询,那么就会出现竞争条件。相反,您应该将
AcceptEx()
与重叠 I/O 或完成端口结合使用。创建套接字的线程可以在每个套接字上调用AcceptEx()
来对每个套接字上的新操作进行排队,然后池化线程可以使用GetQueuedCompletionStatus()
或GetOverlappedResult()
使挂起的连接出队,而不必担心破坏其他线程。一旦连接被接受,接收线程就可以根据需要对其进行处理,然后调用AcceptEx()
为该套接字排队新操作。Do you have the pooled threads all calling this same code? If so, then don't use
WaitForMultipleObjects()
(orWSAWaitForMultipleEvents()
) like this. This kind of model only works reliably if one thread is polling connections. If you have multiple threads polling at the same time, then you have race conditions.Instead, you should use
AcceptEx()
with Overlapped I/O or Completion Ports instead. The thread that creates the sockets can callAcceptEx()
on each socket to queue a new operation on each one, then the pooled threads can useGetQueuedCompletionStatus()
orGetOverlappedResult()
to dequeue a pending connection without worrying about trampling on other threads. Once a connection is accepted, the receiving thread can process it as needed and then callAcceptEx()
to queue a new operation for that socket.这里的每个线程都在进入等待之前设置一个新的
WSAEventSelect
。这将覆盖任何现有的事件选择。这意味着,一旦线程(称为线程 A)接受连接,就没有与套接字关联的事件。要解决此问题,您应该在 switch 中紧随
accept()
之后再次调用WSAEventSelect
。这将在进入任何可能冗长的处理之前立即恢复事件绑定。请注意,如果时机恰到好处,两个线程可能会因同一事件而被唤醒。如果接受失败,您可以通过返回等待循环来解决这个问题,但这有点令人不满意。
因此,不要滚动自己的版本,而是使用 IO此处完成端口。 I/O 完成端口具有许多附加功能,可避免两个线程可能获取同一事件的潜在竞争条件。当您的代码不受 CPU 限制时,他们还会采取措施减少上下文切换。
Each thread here is setting a new
WSAEventSelect
prior to entering the wait. This overwrites any existing event selects. This means that, once a thread (call it thread A) accepts a connection, there is no event associated with the socket.To solve this, you should call
WSAEventSelect
again within your switch, immediately after theaccept()
. This will restore the event binding immediately before going into any potentially lengthy processing.Note that it's possible that two threads may be awoken for the same event, if the timing works out just right. You can hack around that by going back to your wait loop if the accept fails, but this is a bit unsatisfying.
So, instead of rolling your own version, use IO completion ports here. I/O completion ports have a number of additional features, and avoid potential race conditions in which two threads might pick up the same event. They also take steps to reduce context switches when your code is not CPU bound.