2010-05-07 15 views
51

De acuerdo con el borrador final C++ 0x, no hay forma de solicitar que finalice un hilo. Dicho esto, si es necesario, debemos implementar una solución de hágalo usted mismo.C++ 0x interrupción de hilo

Por otro lado Boost :: thread proporciona un mecanismo para interrumpir un hilo en una forma segura manera.

En su opinión, ¿cuál es la mejor solución? ¿Diseñaste tu propio "mecanismo de interrupción" cooperativo o te vuelves nativo?

+3

Merece la pena señalar que si puede matar un hilo, eso puede dejar recursos asignados en la memoria (ya que el hilo se eliminó antes de que se eliminaran). Entonces, creo que esto es, al menos en la mayoría de los casos, una buena decisión. – luiscubal

+9

Hay cosas incluso peores que las filtraciones, como transacciones incompletas, un montón corrupto o bloqueos aún retenidos. – peterchen

+9

si le importa la corrección, realmente no tiene otra opción: entonces los hilos solo * se pueden interrumpir de forma cooperativa, cuando están en estados "intercalados" predefinidos. – jalf

Respuesta

6

No es seguro finalizar un hilo, ya que no tendría control sobre el estado de ninguna estructura de datos en la que se estaba trabajando en ese momento.

Si desea interrumpir un hilo en ejecución, debe implementar su propio mecanismo. En mi humilde opinión, si lo necesita, su diseño no está preparado para múltiples hilos.

Si solo quieres esperar a que termine un hilo, utiliza join() o un futuro.

+52

Hay muchas razones para interrumpir un hilo en ejecución, las cuales no implican un mal diseño. –

+6

Estoy de acuerdo con Chris O. – deuberger

+28

-1, Ignoras por completo el * modo * de interrupción que se logra en el impulso. Es una forma determinística de lanzar una excepción desde algunos puntos predefinidos. La integridad de los datos en los que estaba trabajando el hilo está garantizada por la seguridad de las excepciones. – ybungalobill

2

Estoy de acuerdo con esta decisión. Por ejemplo, .NET permite abortar cualquier hilo de trabajo, y nunca uso esta característica y no recomiendo hacerlo a ningún programador profesional. Quiero decidirme a mí mismo, cuándo se puede interrumpir un hilo de trabajo y cuál es la forma de hacerlo. Es diferente para hardware, E/S, UI y otros hilos. Si el hilo se puede detener en cualquier lugar, esto puede causar un comportamiento del programa indefinido con la gestión de recursos, transacciones, etc.

15

Todas las especificaciones de idioma indican que el soporte no está incorporado en el idioma. boost::thread::interrupt necesita un poco de apoyo de la función del hilo, también:

Cuando el hilo interrumpido ejecuta a continuación uno de los puntos de interrupción (o si está actualmente bloqueado mientras se ejecutan uno)

es decir, cuando la función del hilo no le da a la persona que llama la oportunidad de interrumpir, usted todavía está atascado.

No estoy seguro de lo que quiere decir con "going native" - ​​no hay soporte nativo, a menos que esté embelesado al boost:threads.

Aún así, utilizaría un mecanismo explícito. Tienes que pensar en tener suficientes puntos de interrupción de todos modos, ¿por qué no hacerlos explícitos? El código adicional suele ser marginal en mi experiencia, aunque es posible que necesite cambiar algunas esperas de un solo objeto a múltiples objetos, lo que, dependiendo de su biblioteca, puede parecer más feo.


También se podría tirar de los "no usar excepciones para flujo de control", pero en comparación con jugar un poco con las discusiones, esto es sólo una guía.

+0

La otra opción, también mencionada en http://www2.research.att.com/~bs/C++0xFAQ.html#std-thread, es 'going native', que usa thread :: native_handle() para obtener acceso al identificador de subprocesos del sistema operativo para implementar la interrupción de subprocesos. –

+0

Gracias, Nicola. La versión nativa de IMO frente a la biblioteca depende de otras cosas; en general, no ** confío ** en 'interrupt'. – peterchen

5

La implementación de una solución de "hágalo usted mismo" tiene más sentido, y realmente no debería ser tan difícil de hacer. Necesitará una variable compartida que lea/escriba sincrónicamente, indicando si se le pide al hilo que finalice, y su hilo lee periódicamente de esta variable cuando se encuentra en un estado en el que puede interrumpirse de forma segura. Cuando desee interrumpir un hilo, simplemente escriba sincrónicamente esta variable y luego se unirá al hilo. Suponiendo que coopera de forma adecuada, debe tener en cuenta que la variable se ha escrito y cerrado, lo que hace que la función de unión ya no se bloquee.

Si fuera nativo, no ganaría nada con él; simplemente descartaría todos los beneficios de un mecanismo de enhebrado OOP estándar y multiplataforma. Para que su código sea correcto, el hilo debería cerrarse cooperativamente, lo que implica la comunicación descrita anteriormente.

5

No es seguro finalizar un hilo de forma preventiva porque el estado de todo el proceso se vuelve indeterminado después de ese punto. El hilo podría haber adquirido una sección crítica antes de ser terminado. Esa sección crítica ahora nunca será lanzada. El montón podría quedar bloqueado permanentemente, y así sucesivamente.

La solución boost::thread::interrupt funciona solicitando amablemente. Solo interrumpirá un hilo haciendo algo que sea interrumpible, como esperar en una variable de condición Boost.Thread, o si el hilo hace una de estas cosas después de que se invoca la interrupción. Incluso entonces, el hilo no se coloca sin ceremonias en la picadora de carne ya que, digamos, la función TerminateThread de Win32 sí lo hace, simplemente induce una excepción que, si ha sido un codificador de buen comportamiento y utilizó RAII en todas partes, se limpiará después sí mismo y con gracia salir del hilo.

10

Usar el identificador nativo para cancelar un hilo es una mala opción en C++ ya que necesita destruir todos los objetos asignados de pila. Esta fue la razón principal por la que no incluyeron una operación de cancelación.

Boost.Thread proporciona un mecanismo de interrupción, que debe agruparse en cualquier primitiva de espera. Como esto puede ser costoso como un mecanismo general, el estándar no lo ha incluido.

Tendrá que implementarlo usted mismo. Vea mi respuesta here a una pregunta similar sobre cómo implementar esto usted mismo. Para completar la solución, debe interrumpirse una interrupción cuando la interrupción es verdadera y el hilo debe detectar esta interrupción y finalizar.

4

Mi implementación de subprocesos utiliza el idioma pimpl, y en la clase Impl tengo una versión para cada sistema operativo que soporte y también una que usa boost, por lo que puedo decidir cuál usar al construir el proyecto.

Decidí hacer dos clases: una es Thread, que solo tiene los servicios básicos, provistos por OS; y el otro es SafeThread, que hereda de Thread y tiene un método para la interrupción colaborativa.

El subproceso tiene un método de terminación() que produce una terminación intrusiva. Es un método virtual que está sobrecargado en SafeThread, donde señala un objeto de evento. Hay un método (estático) yeld() que el hilo en ejecución debe llamar de vez en cuando; este método verifica si el objeto del evento está señalizado y, si es así, arroja una excepción atrapada en la persona que llama del punto de entrada del hilo, terminando así el hilo. Cuando lo hace, señala un segundo objeto de evento para que la persona que llama de terminate() sepa que el hilo se detuvo de manera segura.

Para los casos en los que existe riesgo de interbloqueo, SafeThread :: terminate() puede aceptar un parámetro de tiempo de espera excedido. Si el tiempo de espera expira, llama a Thread :: terminate(), matando intrusivamente el hilo. Este es un último recurso cuando tienes algo que no puedes controlar (como una API de un tercero) o en situaciones en las que un interbloqueo hace más daño que las fugas de recursos y cosas por el estilo.

Espero que esto sea útil para su decisión y le dará una imagen lo suficientemente clara sobre mis opciones de diseño. Si no, puedo publicar fragmentos de código para aclarar si lo desea.

+0

Sin ofender, pero ¿qué tiene que ver tu implementación de subproceso particular con 'std :: thread' y' boost :: thread'? –

+0

Disculpe si no fui lo suficientemente claro, la recomendación que quería dar era no usar ninguno de ellos directamente, sino tener sus propias clases contenedoras que podrían usarlos internamente o no, y de acuerdo con su elección, complementarlos con bajo nivel código sin hacer que su aplicación dependa de él. Dado el estado actual del soporte de subprocesos múltiples en C++, creo que este enfoque es necesario y resuelve el problema planteado. En pocas palabras, mi respuesta a la pregunta sería: escriba sus propias clases, pero no necesariamente el código que las respalde: use boost, std o su propio código cuando sea conveniente. –

5

Aquí está mi humilde implementación de un anulador de hilo (para C++ 0x). Espero que sea útil.

// Class cancellation_point 
#include <mutex> 
#include <condition_variable> 

struct cancelled_error {}; 

class cancellation_point 
{ 
public: 
    cancellation_point(): stop_(false) {} 

    void cancel() { 
     std::unique_lock<std::mutex> lock(mutex_); 
     stop_ = true; 
     cond_.notify_all(); 
    } 

    template <typename P> 
    void wait(const P& period) { 
     std::unique_lock<std::mutex> lock(mutex_); 
     if (stop_ || cond_.wait_for(lock, period) == std::cv_status::no_timeout) { 
      stop_ = false; 
      throw cancelled_error(); 
     } 
    } 
private: 
    bool stop_; 
    std::mutex mutex_; 
    std::condition_variable cond_; 
}; 


// Usage example 
#include <thread> 
#include <iostream> 

class ThreadExample 
{ 
public: 
    void start() { 
     thread_ = std::unique_ptr<std::thread>(
      new std::thread(std::bind(&ThreadExample::run, this))); 
    } 
    void stop() { 
     cpoint_.cancel(); 
     thread_->join(); 
    } 
private: 
    void run() { 
     std::cout << "thread started\n"; 
     try { 
      while (true) { 
       cpoint_.wait(std::chrono::seconds(1)); 
      } 
     } catch (const cancelled_error&) { 
      std::cout << "thread cancelled\n"; 
     } 
    } 
    std::unique_ptr<std::thread> thread_; 
    cancellation_point cpoint_; 
}; 

int main() { 
    ThreadExample ex; 
    ex.start(); 
    ex.stop(); 
    return 0; 
}