2010-03-21 12 views
22

Sockets on Linux questionWake up thread blocked on accept() call

Tengo un hilo de trabajo bloqueado en una llamada a accept(). Simplemente espera una conexión de red entrante, la maneja y luego vuelve a escuchar la siguiente conexión.

Cuando es hora de que el programa salga, ¿cómo señalo este hilo de trabajo de red (desde el hilo principal) para regresar desde la llamada accept() mientras aún puedo salir fácilmente de su ciclo y manejar su código de limpieza .

Algunas cosas que me trataron:

  1. pthread_kill para enviar una señal. Se siente demasiado optimista para hacer esto, además de que no permite de manera confiable que el hilo haga su lógica de apagado. También hace que el programa finalice también. Me gustaría evitar las señales si es posible.

  2. pthread_cancel. Lo mismo que arriba. Es una muerte dura en el hilo. Eso, y el hilo puede estar haciendo otra cosa.

  3. Cerrando el conector de escucha del hilo principal para hacer que accept() aborte. Esto no funciona de manera confiable.

Algunas limitaciones:

Si la solución consiste en hacer que la toma no bloqueante escucha, que está muy bien. Pero no quiero aceptar una solución que implique el inicio de subprocesos a través de una llamada de selección cada pocos segundos para verificar la condición de salida.

La condición del hilo para salir no puede estar vinculada al proceso de salida.

Básicamente, la lógica que busco es la siguiente.

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()' envía una señal a un hilo, nada más y nada menos. Y no necesariamente "* hace que el programa termine *". – alk

+0

"* pthread_cancel. Lo mismo que arriba. *" Err, ¿qué? No, 'pthread_cancel()' hace algo completamente diferente a 'pthread_kill()'! Por favor, RTFM. – alk

+2

Una forma fácil de obtener accept() para devolver sería abrir una conexión TCP al puerto en el que está escuchando la llamada accept(). –

Respuesta

16

Se puede utilizar un tubo de notificar a la secuencia que quiera que salga. Luego puede tener una llamada select() que selecciona tanto en el conducto como en el puerto de escucha.

Por ejemplo (compila pero no probado completamente):

// 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); 
} 

Entonces select con receiverFd(), y notificar la terminación usando notify().

+0

Tengo mucha curiosidad por esto, no he usado tuberías con mucha frecuencia. ¿Tiene un código que podría publicar para demostrar esto? :) –

+0

Gracias. De hecho, estaba pensando en este enfoque cuando publiqué la pregunta. Está limpio y funciona muy bien. – selbie

0

Cierre la toma de audición y aceptar devolverá un error.

¿Qué no funciona de manera confiable con esto? Describe los problemas que estás enfrentando.

+4

Llamar cerca de un socket no activará una llamada de aceptación de bloqueo que está en otro hilo. Lo probé en Linux, no funciona. Aunque, creo que esta técnica funciona en Windows, que es lo que he hecho para que los hilos IOCP se activen. – selbie

+0

Gracias, no sabía que ... –

45

Cierre el socket utilizando la llamada shutdown(). Esto activará los hilos bloqueados en él, manteniendo el descriptor de archivo válido.

close() en un descriptor que otro hilo B está utilizando es intrínsecamente peligroso: otro hilo C puede abrir un nuevo descriptor de archivo que el hilo B utilizará en lugar del cerrado. dup2() a /dev/null evita ese problema, pero no activa los hilos bloqueados de manera confiable.

Tenga en cuenta que shutdown() solo funciona en sockets - para otros tipos de descriptores es probable que necesite los enfoques de selección + pipe-to-self o cancelación.

+0

Esta solución también funciona. Así que le daré a Douglas la victoria, ya que su solución es aplicable a otros problemas de enhebrado. Pero daré un "golpe" a tu respuesta para que puedas obtener algunos puntos. :) – selbie

0

pthread_cancel para cancelar un hilo bloqueado en accept() es arriesgado si la implementación pthread no implementa la cancelación correctamente, es decir, si el hilo creó un socket, justo antes de volver a su código, se llama pthread_cancel() para ello , el hilo se cancela y el socket recién creado se filtra. Aunque FreeBSD 9.0 y posterior no tiene ese problema de condición de carrera, primero debe verificar su sistema operativo.