¿Cómo unir un hilo que está colgando en el bloqueo de IO?


Tengo un subproceso corriendo en segundo plano que está leyendo eventos de un dispositivo de entrada de una manera de bloqueo, ahora cuando salgo de la aplicación quiero limpiar el subproceso correctamente, pero no puedo simplemente ejecutar un pthread_join() porque el subproceso nunca saldría debido al IO de bloqueo.

¿Cómo puedo resolver adecuadamente esa situación? ¿Debo enviar un pthread_kill(theard, SIGIO) o un pthread_kill (theard, SIGALRM) para romper el bloque? ¿Es alguna de esas la señal correcta? ¿O hay otro manera de resolver esta situación y dejar que el hilo hijo salga del bloqueo leer?

Actualmente un poco perplejo ya que ninguno de mis googleos encontró una solución.

Esto es en Linux y usando pthreads.

Edit: Jugué un poco con SIGIO y SIGALRM, cuando no instalo un manejador de señal rompen el IO de bloqueo, pero dan un mensaje en la consola ("I / O possible") pero cuando instalo un manejador de señal, para evitar ese mensaje, ya no rompen el IO de bloqueo, por lo que el hilo no termina. Así que estoy de vuelta al primer paso.

Author: Grumbel, 2008-10-15

13 answers

Vieja pregunta que muy bien podría obtener una nueva respuesta a medida que las cosas han evolucionado y una nueva tecnología está ahora disponible para mejor manejar señales en hilos.

Desde el kernel de Linux 2.6.22, el sistema ofrece una nueva función llamada signalfd() que se puede usar para abrir un descriptor de archivo para un conjunto dado de señales Unix (fuera de las que matan directamente un proceso.)

// defined a set of signals
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
// ... you can add more than one ...

// prevent the default signal behavior (very important)
sigprocmask(SIG_BLOCK, &set, nullptr);

// open a file descriptor using that set of Unix signal
f_socket = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);

Ahora puede usar las funciones poll() o select() para escuchar la señal a lo largo del archivo más habitual descriptor (socket, archivo en disco, etc. estabas escuchando.

El NO BLOQUE es importante si desea un bucle que pueda verificar señales y otros descriptores de archivo una y otra vez (es decir, también es importante en su otro descriptor de archivo).

Tengo una implementación que funciona con (1) temporizadores, (2) sockets, (3) pipes, (4) señales Unix, (5) archivos regulares. En realidad, realmente cualquier descriptor de archivo más temporizador.

Https://github.com/m2osw/snapcpp/blob/master/snapwebsites/libsnapwebsites/src/snapwebsites/snap_communicator.cpp
https://github.com/m2osw/snapcpp/blob/master/snapwebsites/libsnapwebsites/src/snapwebsites/snap_communicator.h

También le pueden interesar bibliotecas como libevent

 3
Author: Alexis Wilke,
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
2017-07-07 00:36:21

Yo también recomendaría usar un select o algún otro medio no basado en señal para terminar su hilo. Una de las razones por las que tenemos hilos es para tratar de escapar de la locura de la señal. Dicho esto...

Generalmente se usa pthread_kill() con SIGUSR1 o SIGUSR2 para enviar una señal al hilo. Las otras señales sugeridas SIG SIGTERM, SIGINT, SIGKILL have tienen una semántica de todo el proceso que puede que no le interese.

En cuanto al comportamiento cuando enviaste la señal, mi conjetura es que tiene que ver con cómo manejaste la señal. Si no tiene un controlador instalado, se aplica la acción predeterminada de esa señal, pero en el contexto del subproceso que recibió la señal. Así que SIGALRM, por ejemplo, sería "manejado" por su hilo, pero el manejo consistiría en terminar el proceso probably probablemente no el comportamiento deseado.

La recepción de una señal por el hilo generalmente la romperá de una lectura con EINTR, a menos que esté verdaderamente en ese estado ininterrumpido como mencionado en una respuesta anterior. Pero creo que no lo es, o tus experimentos con SIGALRM y SIGIO no habrían terminado el proceso.

Es su lectura tal vez en una especie de bucle? Si la lectura termina con -1 return, entonces salga de ese bucle y salga del hilo.

Puedes jugar con este código muy descuidado que armé para probar mis suposiciones am Estoy a un par de zonas horarias de mis libros POSIX en este momento...

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <signal.h>

int global_gotsig = 0;

void *gotsig(int sig, siginfo_t *info, void *ucontext) 
{
        global_gotsig++;
        return NULL;
}

void *reader(void *arg)
{
        char buf[32];
        int i;
        int hdlsig = (int)arg;

        struct sigaction sa;
        sa.sa_handler = NULL;
        sa.sa_sigaction = gotsig;
        sa.sa_flags = SA_SIGINFO;
        sigemptyset(&sa.sa_mask);

        if (sigaction(hdlsig, &sa, NULL) < 0) {
                perror("sigaction");
                return (void *)-1;
        }
        i = read(fileno(stdin), buf, 32);
        if (i < 0) {
                perror("read");
        } else {
                printf("Read %d bytes\n", i);
        }
        return (void *)i;
}

main(int argc, char **argv)
{
        pthread_t tid1;
        void *ret;
        int i;
        int sig = SIGUSR1;

        if (argc == 2) sig = atoi(argv[1]);
        printf("Using sig %d\n", sig);

        if (pthread_create(&tid1, NULL, reader, (void *)sig)) {
                perror("pthread_create");
                exit(1);
        }
        sleep(5);
        printf("killing thread\n");
        pthread_kill(tid1, sig);
        i = pthread_join(tid1, &ret);
        if (i < 0)
                perror("pthread_join");
        else
                printf("thread returned %ld\n", (long)ret);
        printf("Got sig? %d\n", global_gotsig);

}
 14
Author: bog,
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
2008-10-15 08:29:21

La forma canónica de hacer esto es con pthread_cancel, donde el hilo ha hecho pthread_cleanup_push/pop para proporcionar limpieza de cualquier recurso que esté utilizando.

Desafortunadamente esto no se puede usar en código C++, nunca. Cualquier código lib de C++ std, o CUALQUIER try {} catch() en la pila de llamadas en el momento de pthread_cancel potencialmente segvi matará todo el proceso.

La única solución es manejar SIGUSR1, estableciendo un indicador de parada, pthread_kill(SIGUSR1), luego en cualquier lugar donde el hilo esté bloqueado en E / S, si obtiene EINTR verifique el indicador de parada antes de volver a intentar la E/S. En la práctica, esto no siempre tiene éxito en Linux, no sé por qué.

Pero en cualquier caso es inútil hablar si tiene que llamar a cualquier lib de terceros, porque lo más probable es que tengan un bucle apretado que simplemente reinicia E/S en EINTR. La ingeniería inversa de su descriptor de archivo para cerrarlo tampoco lo cortará: podrían estar esperando un semáforo u otro recurso. En este caso, es simplemente imposible escribir código de trabajo, punto. Sí, esto es totalmente daño cerebral. Hable con los chicos que diseñaron las excepciones de C++ y pthread_cancel. Supuestamente esto puede arreglarse en alguna versión futura de C++. Buena suerte con eso.

 14
Author: qqq,
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-11-18 02:57:39

Su select() podría tener un tiempo de espera, incluso si es poco frecuente, para salir del subproceso con gracia en una determinada condición. Lo sé, las encuestas apestan...

Otra alternativa es tener un pipe para cada hijo y añadirlo a la lista de descriptores de fichero que están siendo vigilados por el subproceso. Envíe un byte a la tubería desde el padre cuando desee que ese hijo salga. Sin sondeo a costa de una tubería por hilo.

 9
Author: Craig McQueen,
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-11-18 02:59:24

Depende de cómo esté esperando IO.

Si el hilo está en el estado "IO ininterrumpible" (mostrado como "D" en la parte superior), entonces realmente no hay absolutamente nada que pueda hacer al respecto. Normalmente, los subprocesos solo ingresan este estado brevemente, haciendo algo como esperar a que una página sea intercambiada (o cargada a demanda, por ejemplo, desde un archivo mmap o una biblioteca compartida, etc.), sin embargo, un error (particularmente de un servidor NFS) podría hacer que permanezca en ese estado por más tiempo.

Realmente no hay manera de escapando de este estado "D". El hilo no responderá a las señales (puedes enviarlas, pero se pondrán en cola).

Si es una función IO normal como read(), write() o una función de espera como select() o poll(), las señales se entregarían normalmente.

 6
Author: MarkR,
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-12-04 23:37:16

Una solución que se me ocurrió la última vez que tuve un problema como este fue crear un archivo (por ejemplo. un tubo) que existió solo con el propósito de despertar hilos de bloqueo.

La idea sería crear un archivo desde el bucle principal (o 1 por hilo, como sugiere el tiempo de espera - esto le daría un control más fino sobre qué hilos se despiertan). Todos los subprocesos que están bloqueando E/S de archivo harían un select (), usando los archivos en los que están tratando de operar, así como el archivo creado por el bucle principal (como miembro del conjunto de descriptores de archivo de lectura). Esto debería hacer que todas las llamadas select() retornen.

El código para manejar este "evento" desde el bucle principal tendría que ser añadido a cada uno de los hilos.

Si el bucle principal necesita activar todos los subprocesos, puede escribir en el archivo o cerrarlo.


No puedo decir con seguridad si esto funciona, ya que una reestructuración significó que la necesidad de probarlo desapareció.

 3
Author: Andrew Edgecombe,
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
2008-10-15 07:16:52

Creo que, como usted dijo, la única manera sería enviar una señal y luego atraparla y tratarla apropiadamente. Las alternativas pueden ser SIGTERM, SIGUSR1, SIGQUIT, SIGHUP, SIGINT, etc.

También puede usar select() en su descriptor de entrada para que solo lea cuando esté listo. Puedes usar select () con un tiempo de espera de, digamos, un segundo y luego verificar si ese hilo debe terminar.

 2
Author: Chris,
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
2008-10-15 05:47:47

Siempre agrego una función " kill" relacionada con la función thread que corro antes de join que asegura que el thread se pueda unir dentro de un tiempo razonable. Cuando un hilo utiliza bloqueo IO intento utilizar el sistema para romper el bloqueo. Por ejemplo, cuando se usa un socket, tendría que matar la llamada shutdown(2)o close(2) en él, lo que haría que la pila de red terminara limpiamente.

La implementación de sockets de Linux es segura para subprocesos.

 1
Author: David Holm,
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
2008-10-15 07:43:01

Me sorprende que nadie haya sugerido pthread_cancel. Recientemente escribí un programa de E/S multihilo y llamé a cancel() y el join() después funcionó muy bien.

Originalmente había probado el pthread_kill() pero terminé terminando todo el programa con las señales que probé.

 1
Author: HUAGHAGUAH,
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
2008-10-25 23:26:06

Si está bloqueando en una biblioteca de terceros que hace bucles en EINTR, es posible que desee considerar una combinación de usar pthread_kill con una señal (USR1, etc.) que llama a una función vacía (no SIG_IGN) con el cierre/reemplazo del descriptor de archivo en cuestión. Al usar dup2 para reemplazar el fd con /dev/null o similar, causará que la biblioteca de terceros obtenga un resultado de fin de archivo cuando reintente la lectura.

Tenga en cuenta que dup()ing el socket original primero, puede evitar necesitar para cerrar el socket.

 1
Author: bdonlan,
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
2009-06-16 20:28:33

Las señales y el hilo son un problema sutil en Linux según las diferentes páginas de manual. ¿Utiliza LinuxThreads, o NPTL (si está en Linux) ?

No estoy seguro de esto, pero creo que el controlador de señal afecta a todo el proceso, por lo que o termina todo el proceso o todo continúa.

Debes usar timed select o poll, y establecer una bandera global para terminar tu hilo.

 0
Author: shodanex,
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
2008-10-15 07:57:20

Creo que el enfoque más limpio tendría el hilo usando variables condicionales en un bucle para continuar.

Cuando se dispara un evento de e/s, el condicional debe ser señalado.

El hilo principal solo podría señalar la condición mientras canaliza el predicado del bucle a false.

Algo como:

while (!_finished)
{
    pthread_cond_wait(&cond);
    handleio();
}
cleanup();

Recuerde usar variables condicionales para manejar correctamente las señales. Pueden tener cosas como 'espurias wakeups'. Así que me gustaría envolver su propia función alrededor de la cond_wait función.

 0
Author: Nicholas Mancuso,
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
2008-10-15 13:58:03
struct pollfd pfd;
pfd.fd = socket;
pfd.events = POLLIN | POLLHUP | POLLERR;
pthread_lock(&lock);
while(thread_alive)
{
    int ret = poll(&pfd, 1, 100);
    if(ret == 1)
    {
        //handle IO
    }
    else
    {
         pthread_cond_timedwait(&lock, &cond, 100);
     }
}
pthread_unlock(&lock);

Thread_alive es una variable específica del hilo que se puede usar en combinación con la señal para matar el hilo.

En cuanto a la sección handle IO, debe asegurarse de que utilizó open con la opción O_NOBLOCK, o si es un socket, hay una bandera similar, puede establecer MSG_NOWAIT??. para otros fds im no está seguro

 0
Author: luke,
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
2008-10-15 16:07:22