Supongamos que estoy iniciando un std::thread
y luego detach()
, por lo que el hilo continúa ejecutándose a pesar de std::thread
que el que una vez lo representó, queda fuera de alcance.
Suponga además que el programa no tiene un protocolo confiable para unir el hilo separado 1 , por lo que el hilo separado aún se ejecuta cuando main()
sale.
No puedo encontrar nada en el estándar (más precisamente, en el borrador N3797 C ++ 14), que describe lo que debería suceder, ni 1.10 ni 30.3 contienen palabras pertinentes.
1 Otra pregunta, probablemente equivalente, es: "¿se puede volver a unir un subproceso separado?", Ya que sea cual sea el protocolo al que se va a unir, la parte de señalización debe realizarse mientras el subproceso todavía se está ejecutando, y el programador del sistema operativo puede decida poner el hilo en reposo durante una hora justo después de que se haya realizado la señalización, sin que el receptor pueda detectar de manera confiable que el hilo realmente terminó.
Si quedarse sin main()
hilos desconectados en ejecución es un comportamiento indefinido, entonces cualquier uso de este std::thread::detach()
es un comportamiento indefinido a menos que el hilo principal nunca salga 2 .
Por lo tanto, quedarse sin main()
hilos separados que se ejecutan debe tener efectos definidos . La pregunta es: dónde (en el estándar C ++ , no POSIX, no documentos del sistema operativo, ...) se definen esos efectos.
2 No se puede unir un hilo separado (en el sentido de std::thread::join()
). Usted puede esperar los resultados de las discusiones separadas (por ejemplo a través de un futuro a partir de std::packaged_task
, o por un semáforo contador o una bandera y una variable de condición), pero eso no garantiza que el mensaje ha terminado de ejecutarse . De hecho, a menos que poner la parte de señalización en el destructor del primer objeto automático del hilo, no será , en general, ser de código (destructores) que se ejecutan después de que el código de señalización. Si el sistema operativo programa el hilo principal para consumir el resultado y salir antes de que el hilo separado termine de ejecutar dichos destructores, ¿qué definirá ^ Wis para que suceda?
std::exit
o salir demain
es suficiente, pero no necesario, para satisfacer estos requisitos". (todo el párrafo puede ser relevante) Consulte también [support.start.term] / 8 (std::exit
se llama cuando semain
devuelve)Respuestas:
La respuesta a la pregunta original "qué sucede con un hilo separado cuando
main()
sale" es:Continúa ejecutándose (porque el estándar no dice que está detenido), y eso está bien definido, siempre que no toque ni las variables (automáticas | thread_local) de otros hilos ni los objetos estáticos.
Esto parece estar permitido para permitir administradores de subprocesos como objetos estáticos (nota en [basic.start.term] / 4 lo dice, gracias a @dyp por el puntero).
Los problemas surgen cuando la destrucción de los objetos estáticos ha finalizado, porque la ejecución entra en un régimen en el que solo se puede ejecutar el código permitido en los manejadores de señales ( [basic.start.term] / 1, primera oración ). De la biblioteca estándar de C ++, esa es solo la
<atomic>
biblioteca ( [support.runtime] / 9, segunda oración ). En particular, eso, en general, excluyecondition_variable
(está definido por la implementación si se guarda para usar en un controlador de señal, porque no es parte de<atomic>
).A menos que haya desenrollado su pila en este punto, es difícil ver cómo evitar un comportamiento indefinido.
La respuesta a la segunda pregunta "¿se pueden volver a unir hilos separados?" Es:
Sí, con la
*_at_thread_exit
familia de funciones (notify_all_at_thread_exit()
,std::promise::set_value_at_thread_exit()
, ...).Como se señaló en la nota de pie de página [2] de la pregunta, señalar una variable de condición o un semáforo o un contador atómico no es suficiente para unir un hilo separado (en el sentido de asegurar que el final de su ejecución ha sucedido antes de la recepción de dicha señalización por un subproceso en espera), porque, en general, habrá más código ejecutado después, por ejemplo,
notify_all()
de una variable de condición, en particular los destructores de objetos automáticos y locales de subprocesos.Ejecutar la señalización como lo último que hace el subproceso ( después de que ocurrieron destructores de objetos automáticos y locales de subprocesos ) es para lo que
_at_thread_exit
se diseñó la familia de funciones.Por lo tanto, para evitar un comportamiento indefinido en ausencia de garantías de implementación por encima de lo que requiere el estándar, debe unir (manualmente) un hilo separado con una
_at_thread_exit
función que haga la señalización o hacer que el hilo separado ejecute solo un código que sea seguro para un manejador de señal, también.fuente
exit
terminar de destruir objetos estáticos, ejecutaratexit
controladores, vaciar secuencias, etc. devuelve el control al entorno host, es decir, el proceso se cierra. Si un hilo separado todavía se está ejecutando (y de alguna manera ha evitado un comportamiento indefinido al no tocar nada fuera de su propio hilo), simplemente desaparece en una nube de humo a medida que el proceso sale.main
llamadas enpthread_exit
lugar de regresar o llamarexit
, eso hará que el proceso espere a que finalicen los hilos desconectados y luego llameexit
después de que finalice el último.Separar hilos
De acuerdo a
std::thread::detach
:De
pthread_detach
:Separar hilos es principalmente para ahorrar recursos, en caso de que la aplicación no necesite esperar a que termine un hilo (por ejemplo, demonios, que deben ejecutarse hasta la finalización del proceso):
std::thread
objeto quede fuera del alcance sin unirse, lo que normalmente lleva a una llamada a lastd::terminate()
destrucción.Matar hilos
El comportamiento en la terminación del proceso es el mismo que el del subproceso principal, que al menos podría captar algunas señales. No es tan importante si otros hilos pueden manejar señales o no, ya que uno podría unir o terminar otros hilos dentro de la invocación del controlador de señal del hilo principal. (Pregunta relacionada )
Como ya se dijo, cualquier hilo, ya sea separado o no, morirá con su proceso en la mayoría de los sistemas operativos . El proceso en sí mismo puede terminarse levantando una señal, llamando
exit()
o volviendo de la función principal. Sin embargo, C ++ 11 no puede y no intenta definir el comportamiento exacto del sistema operativo subyacente, mientras que los desarrolladores de una máquina virtual Java seguramente pueden abstraer tales diferencias hasta cierto punto. AFAIK, los procesos exóticos y los modelos de subprocesos generalmente se encuentran en plataformas antiguas (a las que C ++ 11 probablemente no se portará) y en varios sistemas integrados, que podrían tener una implementación de biblioteca de idiomas especial y / o limitada y también soporte de idiomas limitado.Soporte de hilo
Si los subprocesos no son compatibles,
std::thread::get_id()
debería devolver una identificación no válida (construcción predeterminadastd::thread::id
) ya que hay un proceso simple, que no necesita un objeto de subproceso para ejecutarse y el constructor de astd::thread
debería arrojar unstd::system_error
. Así es como entiendo C ++ 11 junto con los sistemas operativos actuales. Si hay un sistema operativo con soporte para subprocesos, que no genera un subproceso principal en sus procesos, avíseme.Hilos de control
Si uno necesita mantener el control sobre un subproceso para un apagado correcto, puede hacerlo utilizando primitivas de sincronización y / o algún tipo de indicadores. Sin embargo, en este caso, configurar un indicador de apagado seguido de una unión es la forma que prefiero, ya que no tiene sentido aumentar la complejidad al separar los hilos, ya que los recursos se liberarían al mismo tiempo de todos modos, donde los pocos bytes del
std::thread
objeto vs. mayor complejidad y posiblemente más primitivas de sincronización deberían ser aceptables.fuente
Considere el siguiente código:
Al ejecutarlo en un sistema Linux, el mensaje de thread_fn nunca se imprime. El sistema operativo se limpia
thread_fn()
tan pronto comomain()
sale. Reemplazart1.detach()
cont1.join()
siempre imprime el mensaje como se esperaba.fuente
El destino del hilo después de que el programa sale es un comportamiento indefinido. Pero un sistema operativo moderno limpiará todos los hilos creados por el proceso al cerrarlo.
Al separar un
std::thread
, estas tres condiciones continuarán siendo válidas:*this
ya no posee ningún hilojoinable()
siempre será igual afalse
get_id()
será igualstd::thread::id()
fuente
detach()
tener un comportamiento indefinido? Difícil de creer ...Cuando el subproceso principal (es decir, el subproceso que ejecuta la función main ()) finaliza, el proceso finaliza y todos los demás subprocesos se detienen.
Referencia: https://stackoverflow.com/a/4667273/2194843
fuente
Para permitir que otros hilos continúen la ejecución, el hilo principal debe terminar llamando a pthread_exit () en lugar de salir (3). Está bien usar pthread_exit en main. Cuando se usa pthread_exit, el subproceso principal dejará de ejecutarse y permanecerá en estado zombie (desactivado) hasta que todos los otros subprocesos salgan. Si está utilizando pthread_exit en el subproceso principal, no puede obtener el estado de retorno de otros subprocesos y no puede realizar la limpieza de otros subprocesos (podría hacerse usando pthread_join (3)). Además, es mejor separar los hilos (pthread_detach (3)) para que los recursos del hilo se liberen automáticamente en la terminación del hilo. Los recursos compartidos no se liberarán hasta que salgan todos los hilos.
fuente