pthread_create - pthreads php
¿Cómo unirse a un hilo que se bloquea bloqueando IO? (13)
Tengo un hilo ejecutándose en segundo plano 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ía debido al bloqueo de 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 desconcertado ya que ninguno de mis Google descubrió 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 IO de bloqueo, pero dan un mensaje en la consola ("E / S posible") pero cuando instalo un manejador de señal , para evitar ese mensaje, ya no rompen el bloqueo IO, por lo que el hilo no termina. Así que estoy de regreso al paso uno.
Una vieja pregunta que bien podría obtener una nueva respuesta a medida que las cosas evolucionan y una nueva tecnología ahora está disponible para manejar mejor las señales en los hilos.
Desde Linux kernel 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 aquellas que matan 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 descriptor de archivo más habitual (zócalo, archivo en el disco, etc.) que estaba escuchando.
El NONBLOCK es importante si desea 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 de 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
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 usar en combinación con la señal para matar el hilo.
en cuanto a la sección IO del manejador, necesita asegurarse de que utilizó open con la opción O_NOBLOCK, o si es un conector similar, puede establecer MSG_NOWAIT. para otros fds no estoy seguro
Creo que el enfoque más limpio tendría el hilo utilizando variables condicionales en un ciclo 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();
Recuerde con las variables condicionales para manejar adecuadamente las señales. Pueden tener cosas como "despertar espurios". Así que envolvería su propia función en la función cond_wait.
Creo que, como dijiste, la única forma 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.
Las señales y el hilo son 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 manejador de señales afecta todo el proceso, por lo que puede terminar todo el proceso o todo continuará.
Debe usar selección o encuesta cronometrada, y establecer un indicador global para terminar su hilo.
Siempre agrego una función " matar " relacionada con la función de subproceso que ejecuto antes de unir que garantiza que el subproceso se pueda unir 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 el shutdown de llamada (2) o close (2), lo que provocaría que la pila de red lo termine limpiamente.
La implementación de socket de Linux es segura para subprocesos.
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 sugiere 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 necesitaba activar todos los hilos, podría 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ó.
Yo también recomendaría usar 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. Eso dijo ...
Generalmente uno 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 enviaste la señal, supongo que tiene que ver con la forma en que 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.
La recepción de una señal por el hilo por lo general saldrá de una lectura con EINTR, a menos que realmente esté 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 quizás en algún tipo de ciclo? Si la lectura termina con -1 return, salga de ese ciclo y salga del hilo.
Puedes jugar con este código tan descuidado que preparé para probar mis suposiciones: estoy un par de zonas horarias lejos 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);
}
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é.
Si está bloqueando en una biblioteca de terceros que realiza bucles en EINTR, puede considerar una combinación de usar pthread_kill con una señal (USR1, etc.) llamando a una función vacía (no SIG_IGN) con el cierre / reemplazo del descriptor de archivo en realidad pregunta. 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 socket original primero, puede evitar tener que cerrar el socket.
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 consiste en manejar SIGUSR1
, establecer un indicador de detención, pthread_kill(SIGUSR1)
, y luego, en cualquier lugar donde el hilo esté bloqueado en E / S, si obtiene EINTR
compruebe el indicador de detención 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.
Su select()
podría tener un tiempo de espera, incluso si no es frecuente, para poder salir del hilo con gracia en una determinada condición. Lo sé, la votación es una mierda ...
Otra alternativa es tener un conducto para cada niño y agregarlo a la lista de descriptores de archivo 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.
Depende de cómo está esperando IO.
Si el hilo está en el estado "IO ininterrumpible" (que se muestra 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 enviarán normalmente.