GNU/Linux >> Tutoriales Linux >  >> Linux

¿Cómo unirse a un hilo que está pendiente de bloquear IO?

Yo también recomendaría usar una selección o algún otro medio no basado en señales para terminar su hilo. Una de las razones por las que tenemos subprocesos es para tratar de alejarnos de la locura de las señales. Dicho esto...

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

En cuanto al comportamiento cuando envió la señal, supongo que tiene que ver con cómo manejó la señal. Si no tiene un controlador instalado, se aplica la acción predeterminada de esa señal, pero en el contexto del hilo que recibió la señal. Entonces, SIGALRM, por ejemplo, sería "manejado" por su hilo, pero el manejo consistiría en terminar el proceso, probablemente no sea el comportamiento deseado.

La recepción de una señal por parte del subproceso generalmente la eliminará de una lectura con EINTR, a menos que esté realmente en ese estado ininterrumpible como se mencionó en una respuesta anterior. Pero creo que no lo es, o sus 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, salga de ese ciclo y salga del hilo.

Puedes jugar con este código muy descuidado que armé para probar mis suposiciones:estoy a un par de zonas horarias de distancia 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);

}

Pregunta antigua que muy bien podría obtener una nueva respuesta a medida que las cosas han evolucionado y ahora hay una nueva tecnología disponible para mejorar manejar señales en subprocesos.

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 determinado de señales de Unix (fuera de aquellas que eliminan un proceso por completo).

// 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 signals
f_socket = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);

Ahora puedes usar el poll() o select() funciones para escuchar la señal a lo largo del descriptor de archivo más habitual (socket, archivo en disco, etc.) que estaba escuchando.

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

Tengo una implementación de este tipo que funciona con (1) temporizadores, (2) enchufes, (3) tuberías, (4) señales Unix, (5) archivos regulares. En realidad, realmente cualquier descriptor de archivo más temporizadores.

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 te pueden interesar bibliotecas como libevent


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

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

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

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


Tu select() podría tener un tiempo de espera, incluso si es poco frecuente, para salir del hilo correctamente en una determinada condición. Lo sé, votar apesta...

Otra alternativa es tener una canalización para cada hijo y agregarla a la lista de descriptores de archivos que el hilo está observando. Envíe un byte a la tubería desde el padre cuando quiera que ese hijo salga. Sin encuestas al costo de una canalización por subproceso.


Linux
  1. Cuatro tareas en paralelo... ¿Cómo hacer eso?

  2. ¿Cómo nombrar un hilo en Linux?

  3. ¿Cómo escribir un controlador de señal para capturar SIGSEGV?

  4. Manejo de señales con múltiples hilos en Linux

  5. ¿Cómo evitar el uso de printf en un controlador de señal?

Cómo instalar la aplicación Signal Messaging en Ubuntu 20.04

¿Cómo establecer el nombre de un hilo en Linux pthreads?

¿Qué hilo maneja la señal?

¿Cómo unir dos archivos CSV?

¿Cómo unir/combinar muchos archivos mp3?

Cómo encontrar la fuente de una señal POSIX