2010-03-21 13 views
22

Gniazda na pytanie LinuxPobudka wątek zablokowany na accept() zadzwoń

Mam wątek roboczy, który jest zablokowany w wywołaniu accept(). Po prostu czeka na przychodzące połączenie sieciowe, obsługuje je, a następnie wraca do nasłuchu na następne połączenie.

Kiedy nadejdzie czas zakończenia programu, w jaki sposób zasygnalizuję ten wątek pracownika sieci (z głównego wątku), aby powrócił z wywołania accept(), wciąż będąc w stanie zręcznie opuścić pętlę i obsłużyć jej kod oczyszczający .

Niektóre rzeczy próbowałem:

  1. pthread_kill wysłać sygnał. Czuje się kludgy, aby to zrobić, a ponadto nie pozwala niezawodnie wątku na jego logikę zamykania. Powoduje także zakończenie programu. Chciałbym unikać sygnałów, jeśli to w ogóle możliwe.

  2. pthread_cancel. Jak powyżej. To ostre zabójstwo na wątku. To i wątek może robić coś innego.

  3. Zamknięcie gniazda nasłuchiwania z głównego wątku w celu wykonania funkcji accept(). To nie działa niezawodnie.

pewne ograniczenia:

Jeżeli rozwiązanie wymaga podejmowania słuchać gniazda nieblokujące, że jest w porządku. Ale nie chcę zaakceptować rozwiązania, które obejmuje wątek budzący się przez wywołanie select co kilka sekund, aby sprawdzić warunki wyjścia.

Warunek wątku do wyjścia nie może być związany z zakończeniem procesu.

Zasadniczo logika, do której zmierzam, wygląda tak.

void* WorkerThread(void* args) 
{ 
    DoSomeImportantInitialization(); // initialize listen socket and some thread specific stuff 

    while (HasExitConditionBeenSet()==false) 
    { 
     listensize = sizeof(listenaddr); 
     int sock = accept(listensocket, &listenaddr, &listensize); 

     // check if exit condition has been set using thread safe semantics 
     if (HasExitConditionBeenSet()) 
     { 
      break; 
     } 

     if (sock < 0) 
     { 
      printf("accept returned %d (errno==%d)\n", sock, errno); 
     } 
     else 
     { 
      HandleNewNetworkCondition(sock, &listenaddr); 
     } 
    } 

    DoSomeImportantCleanup(); // close listen socket, close connections, cleanup etc.. 
    return NULL; 
} 

void SignalHandler(int sig) 
{ 
    printf("Caught CTRL-C\n"); 
} 

void NotifyWorkerThreadToExit(pthread_t thread_handle) 
{ 
    // signal thread to exit 
} 

int main() 
{ 
    void* ptr_ret= NULL; 
    pthread_t workerthread_handle = 0; 

    pthread_create(&workerthread, NULL, WorkerThread, NULL); 

    signal(SIGINT, SignalHandler); 

    sleep((unsigned int)-1); // sleep until the user hits ctrl-c 

    printf("Returned from sleep call...\n"); 

    SetThreadExitCondition(); // sets global variable with barrier that worker thread checks on 

    // this is the function I'm stalled on writing 
    NotifyWorkerThreadToExit(workerthread_handle); 

    // wait for thread to exit cleanly 
    pthread_join(workerthread_handle, &ptr_ret); 

    DoProcessCleanupStuff(); 

} 
+0

'pthread_kill()' wysyła sygnał do wątku, nic więcej, nic mniej. I niekoniecznie "* powoduje, że program się kończy *". – alk

+0

"* pthread_cancel. Tak jak powyżej. *" Err, co? Nie, 'pthread_cancel()' robi coś zupełnie innego niż 'pthread_kill()'! Proszę RTFM. – alk

+2

Jednym z łatwych sposobów na uzyskanie akceptacji() w celu powrotu jest otwarcie połączenia TCP z portem, na którym nasłuchuje połączenie accept(). –

Odpowiedz

16

Za pomocą potoku można powiadomić wątek, który ma zostać zakończony. Następnie możesz wykonać wywołanie select(), które wybiera się zarówno na rurze, jak i na gnieździe nasłuchującym.

Na przykład (kompilacji, ale nie w pełni przetestowane):

// NotifyPipe.h 
#ifndef NOTIFYPIPE_H_INCLUDED 
#define NOTIFYPIPE_H_INCLUDED 

class NotifyPipe 
{ 
     int m_receiveFd; 
     int m_sendFd; 

    public: 
     NotifyPipe(); 
     virtual ~NotifyPipe(); 

     int receiverFd(); 
     void notify(); 
}; 

#endif // NOTIFYPIPE_H_INCLUDED 

// NotifyPipe.cpp 

#include "NotifyPipe.h" 

#include <unistd.h> 
#include <assert.h> 
#include <fcntl.h> 

NotifyPipe::NotifyPipe() 
{ 
    int pipefd[2]; 
    int ret = pipe(pipefd); 
    assert(ret == 0); // For real usage put proper check here 
    m_receiveFd = pipefd[0]; 
    m_sendFd = pipefd[1]; 
    fcntl(m_sendFd,F_SETFL,O_NONBLOCK); 
} 


NotifyPipe::~NotifyPipe() 
{ 
    close(m_sendFd); 
    close(m_receiveFd); 
} 


int NotifyPipe::receiverFd() 
{ 
    return m_receiveFd; 
} 


void NotifyPipe::notify() 
{ 
    write(m_sendFd,"1",1); 
} 

Następnie select z receiverFd() i zawiadomić o wypowiedzeniu za pomocą notify().

+0

Jestem bardzo ciekawy tego, nie używam rur bardzo często. Czy masz kod, który możesz opublikować, aby to pokazać? :) –

+0

Dziękuję. Tak naprawdę myślałem o tym podejściu, kiedy opublikowałem pytanie. Jest czysty i działa świetnie. – selbie

0

Zamknij gniazdo nasłuchiwania i zaakceptuj zwróci błąd.

Co nie działa niezawodnie? Opisz problemy, przed którymi stoisz.

+4

Wywołanie zamknięcia na gnieździe nie spowoduje przejęcia połączenia blokującego, które jest w innym wątku. Próbowałem go na Linuksie - to nie działa. Chociaż sądzę, że ta technika działa w systemie Windows, co zrobiłem, aby uzyskać wątki IOCP do przebudzenia. – selbie

+0

Dzięki, nie wiedziałem, że ... –

45

Zamknij gniazdo przy użyciu połączenia shutdown(). Spowoduje to wzbudzenie wszystkich zablokowanych wątków, zachowując przy tym odpowiedni deskryptor pliku.

close() na inny wątek deskryptor B jest za pomocą niebezpiecznymi: inny wątek C może otworzyć nowe deskryptor który gwint B, następnie użyć zamiast zamkniętego. dup2() a /dev/null na to unika tego problemu, ale nie budzi niezawodnie zablokowanych wątków.

Pamiętaj, że shutdown() działa tylko na gniazdach - w przypadku innych rodzajów deskryptorów prawdopodobnie będziesz potrzebował podejścia select + pipe-to-self lub cancellation.

+0

To rozwiązanie też działa. Tak więc dam Douglas wygrać, ponieważ jego rozwiązanie ma zastosowanie do innych problemów z gwintowaniem. Ale udzielę twojej odpowiedzi "guza", abyś mógł zdobyć punkty. :) – selbie

0

pthread_cancel do anulowania wątku zablokowanego w accept() jest ryzykowne, jeśli implementacja pthread nie implementuje poprawnie anulowania, czyli jeśli wątek utworzył gniazdo, tuż przed powrotem do twojego kodu wywoływana jest funkcja pthread_cancel() , wątek zostanie anulowany, a nowo utworzone gniazdo wycieknie. Chociaż FreeBSD 9.0 i nowsze wersje nie mają takiego problemu z wyścigami, ale powinieneś najpierw sprawdzić swój system operacyjny.

Powiązane problemy