2008-10-15 21 views
23

Tengo un hilo ejecutándose en el fondo que está leyendo eventos de un dispositivo de entrada de forma bloqueante, ahora cuando salgo de la aplicación quiero limpiar el hilo correctamente, pero no puedo simplemente ejecutar un pthread_join() porque el hilo nunca saldrá debido al bloqueo de IO.¿Cómo unir un hilo que se bloquea al bloquear IO?

¿Cómo soluciono adecuadamente esa situación? ¿Debo enviar un pthread_kill (theard, SIGIO) o un pthread_kill (theard, SIGALRM) para romper el bloque? ¿Alguna de esas es la señal correcta? ¿O hay otra forma de resolver esta situación y dejar que el hijo salga de la lectura de bloqueo?

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

Esto está en Linux y usa pthreads.

Editar: Jugué un poco con SIGIO y SIGALRM, cuando no instalo un manejador de señal, rompen el bloqueo IO hacia arriba, pero dan un mensaje en la consola ("E/S posible") pero cuando instale un manejador de señal, para evitar ese mensaje, ya no romperán el IO de bloqueo, por lo que el hilo no terminará. Así que estoy de regreso al paso uno.

+0

qqq parece tener la respuesta correcta, que desafortunadamente tiene muy pocos votos. 'pthread_cancel' es la solución a su problema. –

+0

Mientras el hilo permanezca bloqueado, no puede hacer daño. El problema es si el hilo se despierta mientras estás cerrando las cosas. Entonces, la solución es poner un código después de la línea que bloquea que impida que el hilo haga algo * else * si hay un cierre en progreso. –

+0

Un problema similar y posibles soluciones se discuten allí: [Descriptores de archivos y programas multiproceso] (http://www.ddj.com/hpc-high-performance-computing/212001285) – dmityugov

Respuesta

2

pregunta antiguo, que muy bien podría obtener una nueva respuesta como han evolucionado las cosas y una nueva tecnología ya está disponible para mejor señales de manejar en las discusiones.

Desde Linux kernel 2.6.22, el sistema ofrece una nueva función llamada signalfd() que se puede utilizar para abrir un descriptor de archivo para un conjunto dado de señales Unix (fuera de los que matar en el acto 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 utilizar el poll() o select() funciona para escuchar la señal a lo largo del descriptor de archivo más usual (socket, archivo en disco, etc.) que estaba escuchando.

El NONBLOCK es importante si quiere un bucle 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 archivo).

Tengo una implementación 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

Usted también puede estar interesado por las bibliotecas como libevent

2

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

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

3

Una solución que se me ocurrió la última vez que tuve un problema como este fue crear un archivo (por ejemplo, un conducto) que existía solo con el fin de activar el bloqueo de hilos.

La idea sería crear un archivo desde el ciclo principal (o 1 por hilo, como lo indica el tiempo de espera, esto le daría un control más preciso sobre qué subprocesos se despiertan). Todos los subprocesos que están bloqueando en E/S de archivo harían una selección(), utilizando los archivos en los que están tratando de operar, así como también el archivo creado por el bucle principal (como miembro de la lectura conjunto de descriptores de archivos). Esto debería hacer que regresen todas las llamadas select().

El código para manejar este "evento" del bucle principal debería agregarse a cada uno de los hilos.

Si el ciclo principal debe activar todos los hilos, puede escribir en el archivo o cerrarlo.


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

9

Su select() podría tener un tiempo de espera, incluso si no es frecuente, para poder salir del hilo correctamente en una determinada condición. Lo sé, el sondeo chupa ...

Otra alternativa es tener un conducto para cada niño y agregarlo a la lista de descriptores de archivos que está viendo el hilo. Envíe un byte a la tubería del padre cuando desee que ese hijo salga. Sin votación a costa de una tubería por hilo.

+1

o puede tener un conducto para todos los hilos, el estado "listo" se devuelve desde select/poll a varios hilos esperando en un descriptor de archivo único (siempre que se desencadene por nivel). Entonces, todos los hilos que esperan en un solo tubo "asesino" recibirían la notificación de morir. –

6

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 entran en este estado brevemente, haciendo algo como esperar a que se intercambie una página (o demandada, por ejemplo desde un archivo mmap o una biblioteca compartida, etc.), sin embargo, una falla (particularmente de un servidor NFS) podría causar permanecer en ese estado por más tiempo

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

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

1

siempre agrego una función "kill " relacionado con la función de hilo que corro antes combinación que asegura el hilo será acoplable en un tiempo razonable. Cuando un hilo usa bloqueo IO, intento utilizar el sistema para romper el bloqueo. Por ejemplo, cuando use un socket, habría matado la llamada shutdown (2) o close (2) que causaría que la pila de red lo termine limpiamente.

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

0

Señales e hilo es un problema sutil en Linux de acuerdo con las diferentes páginas man. ¿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 puede finalizar todo el proceso o todo continuará.

Debe usar selección o encuesta cronometrada, y establecer un indicador global para terminar su hilo.

13

Yo también recomendaría utilizar un medio de selección o algún otro medio no basado en señal para terminar su hilo. Una de las razones por las que tenemos hilos es tratar de alejarnos 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 - SIGTERM, SIGINT, SIGKILL - tienen semántica en todo el proceso que puede que no le interese.

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

El recibo de una señal por el hilo por lo general lo separará 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, o sus experimentos con SIGALRM y SIGIO no habrían terminado el proceso.

¿Es su lectura tal vez en algún tipo de ciclo? Si la lectura termina con -1 return, salga de ese ciclo y salga del hilo.

Se puede jugar con este código muy descuidado que arme para poner a prueba mis suposiciones - Soy un par de zonas horarias de distancia de mis libros POSIX en el 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); 

} 
+0

Tiene razón, la lectura() en realidad está en un ciclo while que busca EINTR, ya que está en una biblioteca de terceros, no en mi propio código, me extrañé por completo de ese hecho y esa es la razón por la cual una señal simple no es haciendo lo que esperaba – Grumbel

+0

¿Es posible liberar recursos adquiridos con flockfile usando este método? – Ynv

0

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

Cuando se dispara un evento de E/S, se debe señalar el condicional.

El hilo principal podría simplemente señalar la condición al cambiar el predicado de bucle a falso.

algo como:

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

recuerdo con las variables condicionales para manejar correctamente las señales. Pueden tener cosas como "despertar espurios". Así que envolvería su propia función en la función cond_wait.

0
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 de hilo que se puede utilizar en combinación con la señal para matar el hilo.

en cuanto a la sección IO del manejador que necesita para asegurarse de que utilizó open con la opción O_NOBLOCK, o si es un socket con un indicador similar, puede establecer MSG_NOWAIT ??. para otros fds no estoy seguro

1

Me sorprende que nadie haya sugerido pthread_cancel. Hace poco escribí un programa de E/S de subprocesos múltiples y llamé a cancel() y el join() luego funcionó muy bien.

Intenté originalmente el pthread_kill() pero terminé simplemente terminando todo el programa con las señales que probé.

1

Si estás bloqueando en una biblioteca de terceros que se repite en EINTR, es posible que desee considerar la posibilidad de utilizar una combinación pthread_kill con una señal (USR1 etc) llamando a una función de vacío (no SIG_IGN) con el hecho de cerrar/reemplazar el descriptor de archivo en cuestión. Al usar dup2 para reemplazar el fd con/dev/null o similar, hará que la biblioteca de terceros obtenga un resultado de fin de archivo cuando vuelva a intentar la lectura.

Tenga en cuenta que al duplicar el zócalo original primero, puede evitar tener que cerrar el zócalo.

12

La forma canónica de hacerlo es con pthread_cancel, donde el subproceso ha realizado pthread_cleanup_push/pop para proporcionar la limpieza de los recursos que está utilizando.

Lamentablemente, esto NO se puede utilizar en el código C++, nunca. Cualquier código de C++ std lib, o CUALQUIER try {} catch() en la pila de llamadas en el momento de pthread_cancel potencialmente segvi matando a todo el proceso.

La única solución es manejar SIGUSR1, estableciendo un tope de apoyo, pthread_kill(SIGUSR1), a continuación, en cualquier parte del hilo se bloquea en la I/O, si se obtiene EINTR verificación de la bandera 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, ya que es muy probable que tengan un ciclo cerrado que simplemente reinicie E/S en EINTR. Invertir la ingeniería de su descriptor de archivo para cerrarlo tampoco lo cortará: podrían estar esperando en un semáforo u otro recurso. En este caso, es simplemente imposible escribir código de trabajo, punto. Sí, esto está completamente dañado por el cerebro. Habla con los tipos 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.

Cuestiones relacionadas