Activar el hilo bloqueado en la llamada accept()


Sockets en Linux pregunta

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

Cuando es el momento de que el programa salga, cómo señalo este hilo de trabajo de red (desde el hilo principal) para que regrese de la llamada accept() mientras todavía puedo salir de su bucle y manejar su código de limpieza.

Algunas cosas I intentado:

  1. Pthread_kill para enviar una señal. Se siente kludgy para hacer esto, además de que no permite confiablemente el hilo para hacer su lógica de apagado. También hace que el programa termine también. Me gustaría evitar las señales si es posible.

  2. Pthread_cancel. Igual que arriba. Es un duro asesinato en el hilo. Eso, y el hilo puede estar haciendo otra cosa.

  3. Cerrando el socket listen del hilo principal para hacer que accept() aborteara. Este no funciona de manera confiable.

Algunas restricciones:

Si la solución implica hacer que el socket listen no bloquee, está bien. Pero no quiero aceptar una solución que implique que el hilo se despierte a través de una llamada select cada pocos segundos para verificar la condición de salida.

La condición del hilo para salir puede no estar vinculada al proceso que sale.

Esencialmente, la lógica que voy a buscar se ve así.

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();

}
Author: dbush, 2010-03-21

4 answers

Puede usar una tubería para notificar al hilo que desea que salga. Luego puede tener una llamada select() que selecciona tanto en la tubería como en el zócalo de escucha.

Por ejemplo (compila pero no completamente probado):

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

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

 16
Author: Douglas Leeder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-22 19:59:38

Cierra el socket usando la llamada shutdown(). Esto despertará cualquier hilo bloqueado en él, mientras mantiene el descriptor de archivo válido.

close() en un descriptor, otro hilo B está usando es inherentemente peligroso: otro hilo C puede abrir un nuevo descriptor de archivo que el hilo B usará en lugar del cerrado. dup2() un /dev/null en él evita ese problema, pero no despierta los hilos bloqueados de manera confiable.

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

 46
Author: jilles,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-21 22:45:25

Cierre el socket de escucha y accept devolverá un error.

¿Qué no funciona de forma fiable con esto? Describa los problemas que enfrenta.

 0
Author: Len Holgate,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-21 20:57:56

Pthread_cancel cancelar un subproceso bloqueado en accept() es arriesgado si la implementación de pthread no implementa la cancelación correctamente, es decir, si el subproceso creó un socket, justo antes de regresar a su código, se llama a pthread_cancel() para ello, el subproceso se cancela y se filtra el socket recién creado. Aunque FreeBSD 9.0 y posteriores no tienen un problema de condición de carrera, pero usted debe comprobar su sistema operativo primero.

 0
Author: angel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-29 06:59:27