Pregunta

¿Cuáles son las tensiones entre el subprocesamiento múltiple y la seguridad de excepción en C ++? ¿Hay buenas pautas a seguir? ¿Un subproceso termina debido a una excepción no detectada?

¿Fue útil?

Solución

Creo que el estándar C ++ no menciona el subprocesamiento múltiple: el subproceso múltiple es una característica específica de la plataforma.

No estoy exactamente seguro de lo que dice el estándar C ++ sobre las excepciones no detectadas en general, pero según esta página , lo que sucede está definido por la plataforma, y ??debe averiguarlo en la documentación de su compilador.

En una prueba rápida y sucia que hice con g ++ 4.0.1 (i686-apple-darwin8-g ++ - 4.0.1 para ser específico), el resultado es que terminate () es llamado, que mata a todo el programa. El código que utilicé sigue:

#include <stdio.h>
#include <pthread.h>

void *threadproc(void *x)
{
  throw 0;

  return NULL;
}

int main(int argc, char **argv)
{
  pthread_t t;
  pthread_create(&t, NULL, threadproc, NULL);

  void *ret;
  pthread_join(t, &ret);

  printf("ret = 0x%08x\n", ret);

  return 0;
}

Compilado con g ++ threadtest.cc -lpthread -o threadtest . La salida fue:

terminate called after throwing an instance of 'int'

Otros consejos

C ++ 0x tendrá Idioma Soporte para el transporte de excepciones entre subprocesos para que cuando un subproceso de trabajo arroje una excepción, el subproceso de generación pueda atraparlo o volver a lanzarlo.

De la propuesta:

namespace std {

    typedef unspecified exception_ptr;

    exception_ptr current_exception();
    void rethrow_exception( exception_ptr p );

    template< class E > exception_ptr copy_exception( E e );
}

Una excepción no detectada llamará a terminate () que a su vez llama al terminate_handler (que puede configurar el programa). Por defecto, el terminate_handler llama a abort () .

Incluso si anula el terminate_handler predeterminado, el estándar dice que la rutina que proporcione "terminará la ejecución del programa sin volver a la persona que llama". (ISO 14882-2003 18.6.1.3).

Entonces, en resumen, una excepción no detectada terminará el programa no solo el hilo.

En cuanto a la seguridad del hilo, como Adam Rosenfield dice, eso es algo específico de la plataforma que no se aborda en el estándar.

Esta es la razón principal por la que existe Erlang.

No sé cuál es la convención, pero en mi opinión, sé lo más parecido a Erlang posible. Haga que los objetos del montón sean inmutables y configure algún tipo de protocolo de paso de mensajes para comunicarse entre hilos. Evita las cerraduras. Asegúrese de que el mensaje que pasa sea seguro para excepciones. Mantenga tantas cosas con estado en la pila.

Como otros han discutido, la concurrencia (y la seguridad de los hilos en particular) es un problema arquitectónico que afecta la forma en que diseñas tu sistema y tu aplicación.

Pero me gustaría responder a su pregunta sobre la tensión entre seguridad de excepción y seguridad de hilo.

A nivel de clase, la seguridad de subprocesos requiere cambios en la interfaz. Al igual que la seguridad de excepción. Por ejemplo, es habitual que las clases devuelvan referencias a variables internas, por ejemplo:

class Foo {
public:
  void set_value(std::string const & s);

  std::string const & value() const;
};

Si Foo es compartido por múltiples hilos, los problemas te esperan. Naturalmente, puede poner un mutex u otro candado para acceder a Foo. Pero muy pronto, todos los programadores de C ++ querrían envolver Foo en un "ThreadSafeFoo". Mi opinión es que la interfaz para Foo debe cambiarse a:

class Foo {
public:
  void set_value(std::string const & s);

  std::string value() const;
};

Sí, es más costoso, pero puede hacerse seguro con hilos con cerraduras dentro de Foo. En mi opinión, esto crea una cierta tensión entre la seguridad del hilo y la seguridad de la excepción. O al menos, debe realizar más análisis ya que cada clase utilizada como recurso compartido debe examinarse bajo ambas luces.

Un ejemplo clásico (no recuerdo dónde lo vi primero) está en la biblioteca estándar.

Aquí se explica cómo hacer estallar algo de una cola:

T t;
t = q.front(); // may throw
q.pop();

Esta interfaz es algo obtusa en comparación con:

T t = q.pop();

Pero se hace porque la asignación de copia T puede arrojar. Si la copia se lanza después de que aparece el pop, ese elemento se pierde de la cola y nunca se puede recuperar. Pero dado que la copia ocurre antes de que aparezca el elemento, puede colocar un manejo arbitrario alrededor de la copia desde front () en los bloques try / catch.

La desventaja es que no puede implementar una cola que sea segura para subprocesos con la interfaz de std :: queue debido a los dos pasos involucrados. Lo que es bueno para la seguridad de excepción (separando los pasos que pueden lanzar), ahora es malo para el subprocesamiento múltiple.

Su salvador principal en seguridad de excepción es que las operaciones de puntero son de no lanzamiento. Del mismo modo, las operaciones de puntero pueden hacerse atómicas en la mayoría de las plataformas, por lo que a menudo pueden ser su salvador en código multiproceso. Puedes tener tu pastel y comértelo también, pero es muy difícil.

Hay dos problemas que noté:

  • en g ++ en Linux, la eliminación de un hilo (pthread_cancel) se logra lanzando un " desconocido " excepción. Por un lado, eso te permite limpiar bien cuando se está matando el hilo. Por otro lado, si detecta esa excepción y no la vuelve a lanzar, su código termina con abort (). Por lo tanto, si usted o alguna de las bibliotecas que usa kill threads, no puede tener

    captura (...)

sin

throw;

en su código roscado. Aquí es una referencia a este comportamiento en la web:

  • A veces necesitas transferir excepciones entre hilos. Esto no es algo fácil de hacer: terminamos haciendo algún truco, cuando la solución adecuada es el tipo de clasificación / desmarcación que usaría entre los procesos.

No recomiendo dejar ninguna excepción sin detectar. Envuelva sus funciones de subprocesos de nivel superior en controladores generales que pueden cerrar el programa de manera más elegante (o al menos de manera verbosca).

Creo que lo más importante es recordar que las excepciones no detectadas de otros hilos no se muestran al usuario ni se lanzan al hilo principal. Por lo tanto, debe deformar todo el código que debe ejecutarse en subprocesos diferentes al subproceso principal con bloques try / catch.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top