Hilos y señales POSIX

81

He estado tratando de comprender las complejidades de cómo interactúan los hilos POSIX y las señales POSIX. En particular, me interesa:

  • ¿Cuál es la mejor manera de controlar a qué hilo se envía una señal (suponiendo que no sea fatal en primer lugar)?
  • ¿Cuál es la mejor manera de decirle a otro hilo (que en realidad podría estar ocupado) que ha llegado la señal? (Ya sé que es una mala idea usar variables de condición pthread de un controlador de señales).
  • ¿Cómo puedo manejar de forma segura la transmisión de información de que se ha producido una señal a otros hilos? ¿Es necesario que esto suceda en el manejador de señales? (En general, no quiero matar los otros hilos; necesito un enfoque mucho más sutil).

Como referencia sobre por qué quiero esto, estoy investigando cómo convertir el paquete TclX para admitir subprocesos, o dividirlo y al menos hacer que algunas partes útiles admitan subprocesos. Las señales son una de esas partes de especial interés.

Becarios Donales
fuente

Respuestas:

48
  • ¿Cuál es la mejor manera de controlar a qué hilo se envía una señal?

Como indicó @ zoli2k, nombrar explícitamente un solo hilo para manejar todas las señales que desea manejar (o un conjunto de hilos, cada uno con responsabilidades de señal específicas), es una buena técnica.

  • ¿Cuál es la mejor manera de decirle a otro hilo (que en realidad podría estar ocupado) que la señal ha llegado? [...]
  • ¿Cómo puedo manejar de manera segura la transmisión de información de que se ha producido una señal a otros hilos? ¿Es necesario que esto suceda en el manejador de señales?

No diré "mejor", pero esta es mi recomendación:

Bloquea todas las señales deseadas mainpara que todos los hilos hereden esa máscara de señal. Luego, modele el subproceso de recepción de señales especiales como un bucle de eventos impulsado por señales, enviando las señales recién llegadas como alguna otra comunicación dentro del subproceso .

La forma más sencilla de hacer esto es hacer que el hilo acepte señales en un bucle usando sigwaitinfoosigtimedwait . Luego, el hilo convierte las señales de alguna manera, tal vez transmitiendo un pthread_cond_t, despertando otros hilos con más E / S, colocando un comando en una cola segura para hilos específica de la aplicación, lo que sea.

Alternativamente, el hilo especial podría permitir que las señales se envíen a un manejador de señales, desenmascarando para su entrega solo cuando esté listo para manejar señales. ( sigwaitSin embargo, la entrega de señales a través de manejadores tiende a ser más propensa a errores que la aceptación de señales a través de la familia). En este caso, el manejador de señales del receptor realiza una acción simple y segura para señales asíncronas: establecer sig_atomic_tbanderas, llamar sigaddset(&signals_i_have_seen_recently, latest_sig), write() un byte a una de no bloqueo auto-tubo , etc. a continuación, de nuevo en su bucle principal enmascarado, el hilo se comunica la recepción de la señal a otros hilos como anteriormente.

( ACTUALIZADO @caf señala con razón que los sigwaitenfoques son superiores).

pilcrow
fuente
1
Esa es una respuesta mucho más útil, especialmente porque también se puede usar para manejar el manejo de señales no fatales. ¡Gracias!
Donal Fellows
1
Es más fácil si el subproceso de manejo de señales no instala manejadores de señales en absoluto; en su lugar, recorre en sigwaitinfo()(o sigtimedwait()) y luego los envía al resto de la aplicación como se describe en el último párrafo.
caf
@caf, de hecho. Actualizado
pilcrow
14

De acuerdo con el estándar POSIX, todos los subprocesos deben aparecer con el mismo PID en el sistema y, mediante el uso pthread_sigmask(), puede definir la máscara de bloqueo de señal para cada subproceso.

Dado que se permite definir solo un controlador de señal por PID, prefiero manejar todas las señales en un hilo y enviar pthread_cancel()si es necesario cancelar un hilo en ejecución. Es la forma preferida en contra pthread_kill()ya que permite definir funciones de limpieza para los subprocesos.

En algunos sistemas más antiguos, debido a la falta de soporte del kernel adecuado, los subprocesos en ejecución pueden tener un PID diferente del PID del subproceso principal. Consulte las preguntas frecuentes sobre el manejo de señales con linuxThreads en Linux 2.4 .

zoli2k
fuente
¿Qué significa "implementado" en lo que dice? Además, no es correcto bombardear siempre otros subprocesos en respuesta a una señal (SIGHUP y SIGWINCH requieren más sutileza) y, sin embargo, no es seguro usar variables de condición para informar a otros subprocesos. Mala respuesta.
Donal Fellows
1
Eliminé mi voto negativo, pero aún no es una respuesta suficiente porque no puedo simplemente matar hilos en respuesta a una señal. En algunos casos, voy a hacer cola de eventos localmente en respuesta, en otros tengo que eliminar los hilos con mucho cuidado (por cierto, ya tengo la mayor parte de la maquinaria para hacer esas partes; son las conexiones al sistema operativo señales que faltan).
Donal Fellows
1
@ zoli2k: Recientemente intenté ejecutar make menuconfigcon la rama git master recién clonada de uClibc. No es una elección entre los viejos y los nuevos hilos de Linux NTPL como implementaciones de hilos POSIX, pero la ayuda a partir del año 2012 todavía no recomienda elegir NTPL. Por lo tanto, es común todavía en los sistemas Linux embebidos modernos ver la implementación obsoleta de LinuxThreads utilizada, incluso si el sistema está ejecutando un kernel de Linux suficientemente reciente.
FooF
3

Dónde estoy hasta ahora:

  • Las señales vienen en diferentes clases principales, algunas de las cuales normalmente deberían terminar el proceso de todos modos (SIGILL) y algunas de las cuales nunca necesitan hacer nada (SIGIO; es más fácil simplemente hacer IO asíncrono de todos modos). Esas dos clases no necesitan acción.
  • Algunas señales no necesitan tratarse de inmediato; los gustos de SIGWINCH se pueden poner en cola hasta que sea conveniente (como un evento de X11).
  • Los complicados son aquellos en los que quieres responder interrumpiendo lo que estás haciendo pero sin llegar al extremo de borrar un hilo. En particular, SIGINT en modo interactivo debería dejar las cosas receptivas.

Todavía tengo que ordenar a través signalvs sigaction, pselect, sigwait, sigaltstack, y un montón de otras partes y piezas de POSIX (y no POSIX) de la API.

Becarios Donales
fuente
3

En mi humilde opinión, las señales de Unix V y los subprocesos posix no se mezclan bien. Unix V es 1970. POSIX es 1980;)

Hay puntos de cancelación y si permite señales y pthreads en una aplicación, eventualmente terminará escribiendo Loops alrededor de cada llamada, lo que sorprendentemente puede devolver EINTR.

Entonces, lo que hice en los (pocos) casos en los que tuve que programar multiproceso en Linux o QNX fue enmascarar todas las señales para todos los subprocesos (excepto uno).

Cuando llega una señal Unix V, el proceso cambia la pila (esa era la mayor cantidad de simultaneidad en Unix V que se podía obtener dentro de un proceso).

Como sugieren las otras publicaciones aquí, podría ser posible ahora, decirle al Sistema, qué hilo posix será la víctima de ese cambio de pila.

Una vez que logró que su hilo de manejo de señales funcionara, la pregunta sigue siendo cómo transformar la información de la señal en algo civilizado que otros hilos pueden usar. Se requiere una infraestructura para las comunicaciones entre subprocesos. Un patrón, útil es el patrón de actor, donde cada uno de sus hilos es un objetivo para algún mecanismo de mensajería en proceso.

Entonces, en lugar de cancelar otros subprocesos o matarlos (u otras cosas raras), debe intentar ordenar la señal del contexto de la señal a su subproceso del controlador de la señal, luego use sus mecanismos de comunicación de patrón de actor para enviar mensajes semánticamente útiles a esos actores, que necesitan la información relacionada con la señal.

usuario2173833
fuente