2011-05-10 19 views
14

Estoy tratando de usar std :: hilo de C++ 11. No pude encontrar nada si es posible tener un std :: hilo dentro de una clase que ejecuta uno de sus miembros de función. Considere el siguiente ejemplo ... En mi intento (a continuación), la función se ejecuta().C++ 11: std :: hilo dentro de una clase que ejecuta un miembro de función con inicialización de hilo en el constructor

Compilo con gcc-4.4 con -std = C++ 0x marca.

#ifndef RUNNABLE_H 
#define RUNNABLE_H 

#include <thread> 

class Runnable 
{ 
    public: 
     Runnable() : m_stop(false) {m_thread = std::thread(Runnable::run,this); } 
     virtual ~Runnable() { stop(); } 
     void stop() { m_stop = false; m_thread.join(); } 
    protected: 
     virtual void run() = 0; 
     bool m_stop; 
    private: 
     std::thread m_thread; 
}; 


class myThread : public Runnable{ 
protected: 
    void run() { while(!m_stop){ /* do something... */ }; } 
}; 

#endif // RUNNABLE_H 

que estoy recibiendo este error y otros: (mismo error con y sin el $ this)

Runnable.h|9|error: no matching function for call to ‘std::thread::thread(<unresolved overloaded function type>, Runnable* const)’| 

Al pasar un puntero.

Runnable.h|9|error: ISO C++ forbids taking the address of an unqualified or parenthesized non-static member function to form a pointer to member function. Say ‘&Runnable::run’| 

Respuesta

15

Ese enfoque es incorrecto.

El problema es que, aunque el objeto todavía está en construcción, su tipo todavía no es el tipo más derivado, sino el tipo del constructor que se está ejecutando. Eso significa que cuando inicia el hilo el objeto sigue siendo un Runnable y la llamada a run() se puede enviar al Runnable::run(), que es puramente virtual, y que a su vez causará un comportamiento indefinido.

Peor aún, puede tener una falsa sensación de seguridad, ya que podría suceder que bajo ciertas circunstancias el hilo que se está iniciando demore el hilo actual para completar el constructor Runnable, e ingrese el myThread objeto, en cuyo caso el nuevo subproceso ejecutará el método correcto, pero cambiará el sistema donde ejecuta el programa (diferente número de núcleos, o la carga del sistema, o cualquier otra circunstancia no relacionada) y el programa se bloqueará en producción .

+1

Y es por eso que necesita un 'start()' para ser una función que hace el trabajo de inicio real. –

+0

No creo que el hilo que toma el tiempo suficiente para programarse cambie el comportamiento, ¿su constructor no hace una copia (rebanando así) el resultado de bind (& Runnable :: run, this)? – Cubbi

+1

@Etienne de Martel: Hay diferentes enfoques, la opción del método 'start()' es claramente uno de ellos. Otro es el enfoque adoptado por boost y C++ 0x: separe el objeto * ejecutable * del objeto * thread *, que hará que la operación se ejecute como argumento para el constructor. Eso asegura que el objeto que se va a ejecutar está completamente construido. Eso es, por supuesto, si no se abre camino en un marco que lo rompe ... –

13

Aquí hay un código para reflexionar sobre: ​​

#ifndef RUNNABLE_H 
#define RUNNABLE_H 

#include <atomic> 
#include <thread> 

class Runnable 
{ 
public: 
    Runnable() : m_stop(), m_thread() { } 
    virtual ~Runnable() { try { stop(); } catch(...) { /*??*/ } } 

    Runnable(Runnable const&) = delete; 
    Runnable& operator =(Runnable const&) = delete; 

    void stop() { m_stop = true; m_thread.join(); } 
    void start() { m_thread = std::thread(&Runnable::run, this); } 

protected: 
    virtual void run() = 0; 
    std::atomic<bool> m_stop; 

private: 
    std::thread m_thread; 
}; 


class myThread : public Runnable 
{ 
protected: 
    void run() { while (!m_stop) { /* do something... */ }; } 
}; 

#endif // RUNNABLE_H 

Algunas notas:

  • Declaración de m_stop como un simple bool como eras es terriblemente insuficiente; Lea sobre las barreras de memoria
  • std::thread::join puede tirar por lo que calificó sin try..catch de un destructor es imprudente
  • std::thread y std::atomic<> son no copiable, por lo Runnable deben marcarse como tal, si no por otra razón que para evitar C4512 advertencias con VC++
+3

No creo que la variable 'm_stop' deba ser' volátil', 'std :: atomic ' se ocupará de los problemas de subprocesamiento múltiple, y 'volátil' no es de ayuda adicional allí. –

+0

@David Rodríguez: §29.6.5/3 en el FDIS dice explícitamente lo contrario. Si el objeto std :: atomic <> no es volátil, los accesos a ese objeto pueden reordenarse; no hay nada especial acerca del tipo std :: atomic <> que cambie este hecho. (re-pegar debido a un error tipográfico no editable) – ildjarn

+2

No noté el error tipográfico en el comentario anterior, pero creo que fue más preciso que la nueva versión. Lo que dice la norma en ese párrafo es que las operaciones en el objeto atómico pueden * fusionarse * (no decir reordenadas). Mi comprensión es que 'std :: atomic i = 0; para (; i <10; i = i + 1) {} 'se puede transformar en' std :: atomic i = 10; '(esto no es factible con las variables' volátiles', donde se requieren todas las lecturas y escrituras en el ejecutable final). Sin embargo, no he tenido tiempo suficiente para * leer * (como en el caso de una digestión completa) de las secciones 'atómicas' del estándar. –

Cuestiones relacionadas