2012-03-02 57 views
8

Estoy trabajando con algún código multiproceso para un proyecto de juego, y me cansé de ordenar el vómito stdout creado por dos hilos usando cout para depurar mensajes al mismo tiempo. Hice algunas investigaciones y miré una pared durante una hora o dos antes de pensar en "algo". El siguiente código usa SFML para mantener el tiempo y el subprocesamiento. SFML mutexes son simplemente secciones críticas envueltas en Windows.Thread safe cout technique. ¿Me estoy perdiendo de algo?

Header:

#include <SFML\System.hpp> 
#include <iostream> 

class OutputStreamHack 
{ 
    public: 
    OutputStreamHack(); 
    ~OutputStreamHack(); 

    ostream& outputHijack(ostream &os); 

    private: 
    sf::Clock myRunTime; 
    sf::Mutex myMutex; 
}; 

static OutputStream OUTHACK; 

ostream& operator<<(ostream& os, const OutputStreamHack& inputValue); 

Implementación:

#include <SFML\System.hpp> 
#include <iostream> 

#include "OutputStreamHack.h" 

using namespace std; 

OutputStreamHack::OutputStreamHack() 
{ 
    myMutex.Unlock(); 
    myRunTime.Reset(); 
} 

OutputStreamHack::~OutputStreamHack() 
{ 
    myMutex.Unlock(); 
    myRunTime.Reset(); 
} 

ostream& OutputStreamHack::outputHijack(ostream &os) 
{ 

    sf::Lock lock(myMutex); 
    os<<"<"<<myRunTime.GetElapsedTime()<<","<<GetCurrentThreadId()<<"> "<<flush; 
    return os; 
} 

ostream& operator<<(ostream& os, const OutputStreamHack& inputValue) 
{ 
    OUTHACK.outputHijack(os); 
    return os; 
} 

Uso:

cout<<OUTHACK<<val1<<val2<<val3....<<endl; 

Ok, así que la forma en que esto funciona es a través de un operador de inserción sobrecargado que impone la seguridad hilo mediante el bloqueo de un iterador en un objeto estático, luego vaciar el búfer. Si entiendo el proceso correctamente (soy principalmente un programador autodidacta), cout procesa los elementos de su cadena de inserción desde el final hasta el principio, pasando una variable ostream por la cadena para cada elemento que se antepondrá a la secuencia. Una vez que llega al elemento OUTHACK, se llama al operador sobrecargado, el mutex está bloqueado y la secuencia se vacía.

He añadido algo de tiempo/información de depuración de id. De identificación a la salida para fines de verificación. Hasta ahora, mi prueba muestra que este método funciona. Tengo varios hilos golpeando con múltiples argumentos, y todo está saliendo en el orden correcto.

Por lo que he leído mientras investigaba este tema, la falta de seguridad de los hilos en cout parece ser un problema bastante común que las personas se topan al aventurarse en la programación de subprocesos. Lo que intento averiguar es si la técnica que estoy usando es una solución simple al problema, o si pienso que soy inteligente pero me falta algo importante.

En mi experiencia, la palabra inteligente cuando se usa para describir la programación es solo una palabra clave para el dolor retrasado. ¿Estoy trabajando en algo aquí, o simplemente persiguiendo pésimos hacks en círculos?

Gracias!

+1

El hecho de que funcione es pura suerte. Solo la salida del tiempo y la identificación del hilo están protegidos por el mutex. Es perfectamente posible obtener otro subproceso entre 'OUTHACK' y' val1'. –

+1

Considere escribir en un 'ostringstream' primero, luego volcar el contenido en una operación a' cout'. 'cout' generalmente será seguro para subprocesos, es solo que el bloqueo es obviamente por llamada, y cada operación' << 'es una llamada distinta .... De esta forma, no hay bloqueo adicional en el código de la aplicación, lo que solo puede reducir paralelismo. –

+0

Relacionado: [¿Está sincronizado/seguro para subprocesos?] (Http://stackoverflow.com/questions/6374264/is-cout-synchronized-thread-safe/6374525#6374525) – legends2k

Respuesta

19

Lo que no es seguro para los hilos aquí no es cout per se. Llama a dos llamadas de función en secuencia. std::cout << a << b es más o menos equivalente a llamar a operator<<(std::cout, a) seguido de operator<<(std::cout, b). Llamar a dos funciones en secuencia no garantiza que se ejecutarán de forma atómica.

Como es, solo el resultado del tiempo y la identificación del hilo está protegido por el mutex. Es perfectamente posible obtener otro subproceso de hilo entre la inserción de OUTHACK y val1, porque el bloqueo ya no se mantiene después de insertar OUTHACK.

Puede tener operator<< para su retorno OutputStreamHackpor valor un objeto que se desbloquea en el destructor. Como los temporales viven hasta el final de cada expresión completa, el código mantendrá el bloqueo "hasta el punto y coma". Sin embargo, debido a que las copias pueden estar involucradas, esto podría ser problemático sin un constructor de movimiento (o un constructor de copia personalizado en C++ 03, similar a auto_ptr 'gasp).

Otra opción es usar la seguridad de hilos existente de cout (garantizado por el lenguaje en C++ 11, pero muchas implementaciones eran seguras para hilos antes). Cree un objeto que transmita todo en un miembro std::stringstream y luego anótelo todo de una vez cuando se destruya.

class FullExpressionAccumulator { 
public: 
    explicit FullExpressionAccumulator(std::ostream& os) : os(os) {} 
    ~FullExpressionAccumulator() { 
     os << ss.rdbuf() << std::flush; // write the whole shebang in one go 
    } 

    template <typename T> 
    FullExpressionAccumulator& operator<<(T const& t) { 
     ss << t; // accumulate into a non-shared stringstream, no threading issues 
     return *this; 
    } 

private: 
    std::ostream& os; 
    std::stringstream ss; 

    // stringstream is not copyable, so copies are already forbidden 
}; 

// using a temporary instead of returning one from a function avoids any issues with copies 
FullExpressionAccumulator(std::cout) << val1 << val2 << val3; 
+2

Ah, veo la brecha en mi forma de pensar . Estaba operando bajo la suposición de que el comportamiento de cout era recursivo más que iterativo, y que estaba trabajando en un flujo local que se fusionó en stdout en el punto donde se procesó OUTHACK. ¡Su técnica parece exactamente lo que me faltaba! Gracias una tonelada. – Chris

Cuestiones relacionadas