c++ - programas - funcion switch en c
¿Cómo salir limpiamente de un programa de C++ con hilos? (4)
Estoy creando múltiples hilos en mi programa. Al presionar Ctrl-C , se llama un manejador de señal. Dentro de un manejador de señales, he puesto por fin la exit(0)
. La cosa es que a veces el programa termina de forma segura, pero las otras veces, aparece un error de tiempo de ejecución que indica
abort() has been called
Entonces, ¿cuál sería la posible solución para evitar el error?
Adicional a la respuesta de algunos programadores y relacionada con la discusión en la sección de comentarios, debe hacer que la marca que controla la terminación de sus hilos como tipo atomic
.
Considere el siguiente caso:
bool done = false;
void pending_thread()
{
while(!done)
{
std::this_thread::sleep(std::milliseconds(1));
}
// do something that depends on working thread results
}
void worker_thread()
{
//do something for pending thread
done = true;
}
Aquí, el subproceso de trabajo puede ser su subproceso main
y también se termina el indicador de su subproceso, pero el subproceso pendiente debe hacer algo con los datos dados al trabajar el subproceso, antes de salir.
este ejemplo tiene una condición de raza y un comportamiento indefinido junto con él, y es realmente difícil encontrar cuál es el problema real en el mundo real.
Ahora la versión corregida usando std::automic
:
std::atomic<bool> done(false);
void pending_thread()
{
while(!done.load())
{
std::this_thread::sleep(std::milliseconds(1));
}
// do something that depends on working thread results
}
void worker_thread()
{
//do something for pending thread
done = true;
}
Puede salir del hilo sin preocuparse por la condición de carrera o UB.
La forma habitual es establecer una bandera atómica (como std::atomic<bool>
) que está verificada por todos los subprocesos (incluido el subproceso principal). Si se establece, los subprocesos salen y el subproceso principal comienza a join
los subprocesos. Entonces puedes salir limpiamente.
Si usa std::thread
para los hilos, esa es una posible razón de los bloqueos que tiene. Debe join
al subproceso antes de que se destruya el objeto std::thread
.
Lo primero que debes aceptar es que el enhebrado es difícil.
Un "programa que usa subprocesos" es tan genérico como un "programa que usa memoria", y su pregunta es similar a "¿cómo no corromper la memoria en un programa que usa memoria?"
La forma en que maneja el problema de los hilos es restringir la forma en que usa los hilos y el comportamiento de los hilos.
Si su sistema de subprocesos es un conjunto de pequeñas operaciones compuestas en una red de flujo de datos, con una garantía implícita de que si una operación es demasiado grande, se divide en operaciones más pequeñas y / o realiza puntos de control con el sistema, luego el apagado se ve muy diferente si tiene un subproceso que carga una DLL externa que luego lo ejecuta durante un tiempo desde 1 segundo hasta 10 horas hasta una duración infinita.
Como la mayoría de las cosas en C ++, la solución de su problema se tratará de la propiedad, el control y (en última instancia) los ataques.
Al igual que los datos en C ++, cada hilo debe ser propiedad. El propietario de un subproceso debería tener un control significativo sobre ese subproceso y poder decirle que la aplicación se está cerrando. El mecanismo de apagado debe ser robusto y probado, e idealmente conectado a otros mecanismos (como el aborto temprano de tareas especulativas).
El hecho de que esté llamando a exit (0) es una mala señal. Implica que el hilo de ejecución principal no tiene una ruta de cierre limpia. Empieza por ahi el controlador de interrupciones debe indicar al hilo principal que el cierre debe comenzar, y luego su hilo principal debe cerrarse con gracia. Todos los marcos de pila deben desenrollarse, los datos deben ser limpiados, etc.
Luego, el mismo tipo de lógica que permite que el cierre limpio y rápido también se aplique a su código de subproceso.
Cualquiera que le diga que es tan simple como una variable de condición / atómico booleano y sondeo le está vendiendo una lista de productos. Eso solo funcionará en casos simples si tiene suerte, y determinar si funciona de manera confiable va a ser bastante difícil.
Otros han mencionado que el manejador de señales establezca std::atomic<bool>
y que todos los demás subprocesos verifiquen periódicamente ese valor para saber cuándo salir.
Ese enfoque funciona bien siempre y cuando todos sus otros hilos se despierten periódicamente de todos modos, con una frecuencia razonable.
Sin embargo, no es del todo satisfactorio si uno o más de sus subprocesos son puramente impulsados por eventos. En un programa impulsado por eventos, se supone que los subprocesos solo se activan cuando hay algo que hacer, lo que significa que podrían ser estar dormido durante días o semanas a la vez. Si se ven obligados a activarse cada (muchos) milisegundos simplemente para sondear una bandera atómica-booleana, eso hace que un programa por lo demás extremadamente eficiente en la CPU sea mucho menos eficiente en la CPU, ya que ahora cada hilo se está despertando a intervalos regulares cortos, 24/7/365. Esto puede ser particularmente problemático si está intentando conservar la vida útil de la batería, ya que puede evitar que la CPU entre en modo de ahorro de energía.
Un enfoque alternativo que evita el sondeo sería este:
- Al inicio, haz que tu hilo principal cree un fd-pipe o socket-pair (llamando a pipe() o socketpair() )
- Haga que su subproceso principal (o posiblemente algún otro subproceso responsable) incluya el zócalo receptor en su lista de
select()
fd_set (o realice una acción similar parapoll()
o cualquier función de espera de IO en la que el subproceso se bloquee) - Cuando se ejecuta el manejador de señales, pídale que escriba un byte (cualquier byte, no importa qué) en el socket de envío.
- Eso hará que la llamada
select()
del subproceso principal vuelva inmediatamente, conFD_ISSET(receivingSocket)
indica verdadero debido al byte recibido - En ese punto, su hilo principal sabe que es hora de que el proceso finalice, por lo que puede comenzar a dirigir todos sus hilos secundarios para que se apaguen (a través de cualquier mecanismo que sea conveniente; booleanos atómicos o tuberías o cualquier otra cosa)
- Después de indicar a todos los subprocesos secundarios que comiencen a cerrarse, el subproceso principal debe llamar a
join()
en cada subproceso secundario, de modo que se pueda garantizar que todos los subprocesos secundarios se hayan ido antes de que main () regrese. (Esto es necesario porque, de lo contrario, existe el riesgo de una condición de carrera: por ejemplo, el código de limpieza posterior a main () podría liberar un recurso ocasionalmente mientras un subproceso secundario aún en ejecución lo estaba usando, lo que provocaría un fallo)