c - WSAWaitForMultipleObjects блокирует любой поток, кроме последнего - PullRequest
1 голос
/ 24 августа 2011

У меня проблема с многопоточным 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

Когда пул содержит только один поток, проблем нет. Когда пул состоит из нескольких потоков, только один поток принимает входящие соединения

Ответы [ 2 ]

0 голосов
/ 24 августа 2011

Каждый поток здесь устанавливает новый WSAEventSelect до для входа в ожидание. Это перезаписывает любые существующие события выбора. Это означает, что, как только поток (назовите его потоком A) принимает соединение, событие не связано с сокетом.

Чтобы решить эту проблему, вы должны снова позвонить на WSAEventSelect внутри коммутатора, сразу после accept(). Это восстановит привязку события непосредственно перед началом любой потенциально длительной обработки.

Обратите внимание, что возможно, что два потока могут быть вызваны для одного и того же события, если синхронизация работает точно. Вы можете обойти это, вернувшись к циклу ожидания, если принять не удалось, но это немного неудовлетворительно.

Таким образом, вместо использования собственной версии, используйте порты завершения ввода-вывода здесь. Порты завершения ввода / вывода имеют ряд дополнительных функций и позволяют избежать потенциальных условий гонки, при которых два потока могут получить одно и то же событие. Они также предпринимают шаги, чтобы уменьшить переключение контекста, когда ваш код не связан с процессором.

0 голосов
/ 24 августа 2011

Все ли потоки пула вызывают этот код?Если так, то не используйте WaitForMultipleObjects() (или WSAWaitForMultipleEvents()), как это.Этот тип модели работает только надежно, если один поток опрашивает соединения.Если у вас есть несколько потоков, опрашивающих одновременно, то у вас есть условия гонки.

Вместо этого вы должны вместо этого использовать AcceptEx() с перекрывающимися портами ввода-вывода или портами завершения.Поток, который создает сокеты, может вызывать AcceptEx() в каждом сокете, чтобы ставить в очередь новую операцию для каждого из них, затем потоки в пуле могут использовать GetQueuedCompletionStatus() или GetOverlappedResult() для удаления ожидающего соединения, не беспокоясь о вытеснении других потоков.Как только соединение принято, принимающий поток может обработать его по мере необходимости, а затем вызвать AcceptEx(), чтобы поставить в очередь новую операцию для этого сокета.

...