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 阻塞任何线程但最后一个的主要内容,如果未能解决你的问题,请参考以下文章

C/C++ floor 函数

C语言数组问题?

关于c++/c

C语言 extern “C”

使用 MetroWerks C/C++ 开发的 C/C++ 资源

Lua与C/C++交互——C/C++调用Lua脚本