Solución:
La respuesta a la pregunta original “¿qué sucede con un hilo separado cuando main()
salidas “es:
Continúa ejecutándose (porque el estándar no dice que esté detenido), y eso está bien definido, siempre que no toque las variables (automáticas | thread_local) de otros subprocesos ni objetos estáticos.
Esto parece estar permitido para permitir administradores de subprocesos como objetos estáticos (nota en [basic.start.term]/ 4 dice tanto, gracias a @dyp por el puntero).
Los problemas surgen cuando la destrucción de objetos estáticos ha terminado, porque entonces la ejecución entra en un régimen donde 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, que, en general,excluye condition_variable
(está definido por la implementación si se guarda para usar en un manejador de señales, 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 los hilos separados” es:
Si, con el *_at_thread_exit
familia de funcionesnotify_all_at_thread_exit()
, std::promise::set_value_at_thread_exit()
, …).
Como se indica en la nota a 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 pasado antes la recepción de dicha señalización por un hilo en espera), porque, en general, habrá más código ejecutado después de, por ejemplo, un notify_all()
de una variable de condición, en particular los destructores de objetos locales de subprocesos y automáticos.
Ejecutar la señalización como lo último que hace el hilo (después destructores de objetos locales de subprocesos y automáticos pasó) es lo que el _at_thread_exit
familia de funciones para la que fue diseñada.
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 un _at_thread_exit
función haciendo la señalización o hacer que el hilo separado se ejecute solamente código que también sería seguro para un manejador de señales.
Separación de hilos
De acuerdo a std::thread::detach
:
Separa el hilo de ejecución del objeto hilo, lo que permite que la ejecución continúe de forma independiente. Todos los recursos asignados se liberarán una vez que salga el hilo.
De pthread_detach
:
La función pthread_detach () indicará a la implementación que el almacenamiento del hilo se puede recuperar cuando ese hilo termina. Si el hilo no ha terminado, pthread_detach () no hará que termine. El efecto de múltiples llamadas a pthread_detach () en el mismo hilo de destino no está especificado.
La separación de subprocesos es principalmente para ahorrar recursos, en caso de que la aplicación no necesite esperar a que termine un subproceso (por ejemplo, demonios, que deben ejecutarse hasta la finalización del proceso):
- Para liberar el asa lateral de la aplicación: se puede dejar
std::thread
objeto sale del alcance sin unirse, lo que normalmente conduce a una llamada astd::terminate()
sobre la destrucción. - Para permitir que el sistema operativo limpie los recursos específicos del subproceso (TCB) automáticamente tan pronto como el subproceso sale, porque especificamos explícitamente, que no estamos interesados en unirnos al subproceso más adelante, por lo tanto, uno no puede unirse a un subproceso ya separado.
Matar hilos
El comportamiento en la terminación del proceso es el mismo que el del hilo principal, que al menos podría captar algunas señales. No es tan importante si otros subprocesos pueden manejar señales o no, ya que uno podría unir o terminar otros subprocesos dentro de la invocación del manejador de señales del subproceso 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í puede terminarse emitiendo una señal, llamando exit()
o volviendo de la función principal. Sin embargo, C ++ 11 no puede ni intenta definir el comportamiento exacto del sistema operativo subyacente, mientras que los desarrolladores de una máquina virtual Java seguramente pueden abstraer esas diferencias hasta cierto punto. AFAIK, los procesos exóticos y los modelos de subprocesos generalmente se encuentran en plataformas antiguas (a las que probablemente no se adaptará C ++ 11) y varios sistemas integrados, que podrían tener una implementación de biblioteca de lenguaje especial y / o limitada y también soporte de lenguaje limitado.
Soporte de hilo
Si los hilos no son compatibles std::thread::get_id()
debe devolver una identificación no válida (construido por defecto std::thread::id
) ya que hay un proceso simple, que no necesita un objeto de hilo para ejecutarse y el constructor de un std::thread
debería lanzar un std::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.
Control de subprocesos
Si uno necesita mantener el control sobre un hilo para un cierre adecuado, puede hacerlo usando primitivas de sincronización y / o algún tipo de banderas. Sin embargo, en este caso, la configuración de una bandera de apagado seguida de una combinación es la forma que prefiero, ya que no tiene sentido aumentar la complejidad separando subprocesos, ya que los recursos se liberarían al mismo tiempo de todos modos, donde los pocos bytes de la std::thread
objeto frente a mayor complejidad y posiblemente más primitivas de sincronización deberían ser aceptables.
Considere el siguiente código:
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
void thread_fn() {
std::this_thread::sleep_for (std::chrono::seconds(1));
std::cout << "Inside thread functionn";
}
int main()
{
std::thread t1(thread_fn);
t1.detach();
return 0;
}
Al ejecutarlo en un sistema Linux, el mensaje de thread_fn nunca se imprime. El sistema operativo realmente se limpia thread_fn()
Tan pronto como main()
salidas. Reemplazo t1.detach()
con t1.join()
siempre imprime el mensaje como se esperaba.