Tenemos una función a la que llama un solo hilo (lo llamamos hilo principal). Dentro del cuerpo de la función, generamos varios subprocesos de trabajo para realizar un trabajo intensivo de la CPU, esperar a que finalicen todos los subprocesos y luego devolver el resultado en el subproceso principal.
El resultado es que la persona que llama puede usar la función de manera ingenua e internamente hará uso de múltiples núcleos.
Todo bien hasta ahora ..
El problema que tenemos es lidiar con las excepciones. No queremos que las excepciones en los hilos de trabajo bloqueen la aplicación. Queremos que la persona que llama a la función pueda capturarlos en el hilo principal. Debemos detectar excepciones en los subprocesos de trabajo y propagarlas al subproceso principal para que continúen desenvolviéndose desde allí.
¿Cómo podemos hacer esto?
Lo mejor que se me ocurre es:
- Detecte una gran variedad de excepciones en nuestros hilos de trabajo (std :: exception y algunas de las nuestras).
- Registre el tipo y mensaje de la excepción.
- Tenga una declaración de cambio correspondiente en el hilo principal que vuelva a generar excepciones de cualquier tipo que se haya registrado en el hilo de trabajo.
Esto tiene la desventaja obvia de que solo admite un conjunto limitado de tipos de excepciones y necesitaría modificaciones cada vez que se agregan nuevos tipos de excepciones.
fuente
Actualmente, la única forma portátil es escribir cláusulas de captura para todos los tipos de excepciones que le gustaría transferir entre subprocesos, almacenar la información en algún lugar de esa cláusula de captura y luego usarla más tarde para volver a generar una excepción. Este es el enfoque adoptado por Boost.Exception .
En C ++ 0x, podrá detectar una excepción
catch(...)
y luego almacenarla en una instancia destd::exception_ptr
usingstd::current_exception()
. A continuación, puede volver a lanzarlo más tarde desde el mismo hilo o desde otro diferente constd::rethrow_exception()
.Si está utilizando Microsoft Visual Studio 2005 o posterior, entonces la biblioteca de subprocesos just :: thread C ++ 0x admite
std::exception_ptr
. (Descargo de responsabilidad: este es mi producto).fuente
Si está utilizando C ++ 11, entonces
std::future
podría hacer exactamente lo que está buscando: puede atrapar automáticamente las excepciones que llegan a la parte superior del hilo de trabajo y pasarlas al hilo principal en el punto questd::future::get
es llamado. (Detrás de escena, esto sucede exactamente como en la respuesta de @AnthonyWilliams; ya se ha implementado para usted).El lado negativo es que no existe una forma estándar de "dejar de preocuparse por" a
std::future
; incluso su destructor simplemente se bloqueará hasta que finalice la tarea. [EDITAR, 2017: El comportamiento del destructor de bloqueo es un error solo de los pseudo-futuros devueltosstd::async
, que nunca debe usar de todos modos. Los futuros normales no bloquean su destructor. Pero aún no puede "cancelar" tareas si está usandostd::future
: las tareas que cumplen la promesa continuarán ejecutándose detrás de escena incluso si nadie está escuchando la respuesta.] Aquí hay un ejemplo de juguete que podría aclarar lo que yo media:Intenté escribir un ejemplo de trabajo similar usando
std::thread
ystd::exception_ptr
, pero algo va mal constd::exception_ptr
(usando libc ++), así que aún no he logrado que funcione. :([EDITAR, 2017:
No tengo idea de qué estaba haciendo mal en 2013, pero estoy seguro de que fue mi culpa.]
fuente
f
y luegoemplace_back
? ¿No podrías simplemente hacerwaitables.push_back(std::async(…));
o estoy pasando por alto algo (se compila, la pregunta es si podría filtrarse, pero no veo cómo)?wait
ing? Algo en la línea “en cuanto falla uno de los trabajos, los demás ya no importan”.async
devuelve un futuro en lugar de otra cosa). Re "Also, is there": No instd::future
, pero vea la charla de Sean Parent "Better Code: Concurrency" o mi "Futures from Scratch" para conocer diferentes formas de implementar eso si no le importa reescribir todo el STL para empezar. :) El término de búsqueda clave es "cancelación".Su problema es que podría recibir múltiples excepciones, de múltiples subprocesos, ya que cada uno podría fallar, quizás por diferentes razones.
Supongo que el hilo principal de alguna manera está esperando a que los hilos terminen para recuperar los resultados, o verificando regularmente el progreso de los otros hilos, y que el acceso a los datos compartidos está sincronizado.
Solución simple
La solución simple sería capturar todas las excepciones en cada hilo, registrarlas en una variable compartida (en el hilo principal).
Una vez que terminen todos los hilos, decida qué hacer con las excepciones. Esto significa que todos los demás subprocesos continuaron su procesamiento, que quizás no sea lo que desea.
Solución compleja
La solución más compleja es que cada uno de sus subprocesos se verifique en puntos estratégicos de su ejecución, si se lanzó una excepción desde otro subproceso.
Si un hilo produce una excepción, se detecta antes de salir del hilo, el objeto de excepción se copia en algún contenedor del hilo principal (como en la solución simple) y alguna variable booleana compartida se establece en verdadera.
Y cuando otro hilo prueba este booleano, ve que la ejecución debe ser abortada y aborta de una manera elegante.
Cuando todos los subprocesos abortaron, el subproceso principal puede manejar la excepción según sea necesario.
fuente
Una excepción lanzada desde un hilo no será detectable en el hilo principal. Los subprocesos tienen diferentes contextos y pilas y, por lo general, no se requiere que el subproceso padre permanezca allí y espere a que los hijos terminen, para poder detectar sus excepciones. Simplemente no hay lugar en el código para esa captura:
Deberá detectar las excepciones dentro de cada hilo e interpretar el estado de salida de los hilos en el hilo principal para volver a lanzar cualquier excepción que pueda necesitar.
Por cierto, en la ausencia de una captura en un hilo, es específico de la implementación si el desenrollado de la pila se realizará, es decir, es posible que los destructores de sus variables automáticas ni siquiera se llamen antes de que termine. Algunos compiladores hacen eso, pero no es obligatorio.
fuente
¿Podría serializar la excepción en el hilo de trabajo, transmitirla al hilo principal, deserializar y lanzarla de nuevo? Espero que para que esto funcione, todas las excepciones tendrían que derivar de la misma clase (o al menos un pequeño conjunto de clases con la instrucción de cambio nuevamente). Además, no estoy seguro de que se puedan serializar, solo estoy pensando en voz alta.
fuente
De hecho, no existe una forma buena y genérica de transmitir excepciones de un hilo al siguiente.
Si, como debería, todas sus excepciones derivan de std :: exception, entonces puede tener una captura de excepción general de nivel superior que de alguna manera enviará la excepción al hilo principal donde se lanzará nuevamente. El problema es que pierdes el punto de partida de la excepción. Probablemente pueda escribir código dependiente del compilador para obtener esta información y transmitirla.
Si no todas sus excepciones heredan std :: exception, entonces está en problemas y tiene que escribir muchas capturas de nivel superior en su hilo ... pero la solución aún se mantiene.
fuente
Deberá realizar una captura genérica para todas las excepciones en el trabajador (incluidas las excepciones que no son estándar, como las infracciones de acceso) y enviar un mensaje desde el hilo del trabajador (¿supongo que tiene algún tipo de mensaje en su lugar?) Al controlador hilo, que contiene un puntero en vivo a la excepción, y volver a lanzar allí creando una copia de la excepción. Luego, el trabajador puede liberar el objeto original y salir.
fuente
Consulte http://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_exception_ptr.html . También es posible escribir una función contenedora de cualquier función que llame para unirse a un subproceso secundario, que automáticamente vuelve a lanzar (usando boost :: rethrow_exception) cualquier excepción emitida por un subproceso secundario.
fuente