2012-09-28 79 views
12

quiero asegurar que sólo un hilo a la vez puede ejecutar un método de mi clase de C++. En otras palabras, haz que la clase se comporte como Monitor.Haciendo una clase de C++ de un monitor (en el sentido concurrente)

¿Existe un patrón, a plantillas manera de hacer esto, o alguna clase de impulso que pueda usar? Porque mi única idea hasta ahora es agregar un miembro de la Sección Crítica, y adquirirlo al comienzo de cada método y liberarlo al final (usando RAII, por supuesto). Pero eso parece muy redundante, y no puedo reutilizarlo para alguna otra clase.

+0

Es difícil para mí imaginar haciendo esto con menos código. El código requerido para la solución propuesta es una línea para la clase (el miembro mutex) más una línea para cada función. No creo que vaya a ser más pequeño sin un soporte de lenguaje específico. ¿Es esta tu pregunta? Si es así, la respuesta es "C++ no tiene soporte de idiomas para hacer monitores". :) –

+1

¿Por qué no usa un mutex, está ampliamente disponible. Inventar sus propias primitivas de sincronización tiene una habilidad especial para enviar mal. –

+0

Ver [Boost.Thread] (http://www.boost.org/libs/thread/) en caso de que no fuera lo suficientemente obvio. : -] – ildjarn

Respuesta

4

Usted puede lograr esto con un cierto uso juicioso de operator-> yc moderna ++ que proporciona una sintaxis mucho más limpia que la respuesta previamente aceptada:

template<class T> 
class monitor 
{ 
public: 
    template<typename ...Args> 
    monitor(Args&&... args) : m_cl(std::forward<Args>(args)...){} 

    struct monitor_helper 
    { 
     monitor_helper(monitor* mon) : m_mon(mon), m_ul(mon->m_lock) {} 
     T* operator->() { return &m_mon->m_cl;} 
     monitor* m_mon; 
     std::unique_lock<std::mutex> m_ul; 
    }; 

    monitor_helper operator->() { return monitor_helper(this); } 
    monitor_helper ManuallyLock() { return monitor_helper(this); } 
    T& GetThreadUnsafeAccess() { return m_cl; } 

private: 
    T   m_cl; 
    std::mutex m_lock; 
}; 

La idea es que se utiliza el operador de flecha para acceder al objeto subyacente, pero que devuelve un objeto auxiliar que bloquea y luego desbloquea el mutex alrededor de su llamada de función. Luego, a través de la magia del lenguaje aplicando repetidamente operator->, obtiene una referencia al objeto subyacente.

Uso:

monitor<std::vector<int>> threadSafeVector {5}; 

threadSafeVector->push_back(0); 
threadSafeVector->push_back(1); 
threadSafeVector->push_back(2); 

// Create a bunch of threads that hammer the vector 
std::vector<std::thread> threads; 
for(int i=0; i<16; ++i) 
{ 
    threads.push_back(std::thread([&]() 
    { 
     for(int i=0; i<1024; ++i) 
     { 
      threadSafeVector->push_back(i); 
     } 
    })); 
} 

// You can explicitely take a lock then call multiple functions 
// without the overhead of a relock each time. The 'lock handle' 
// destructor will unlock the lock correctly. This is necessary 
// if you want a chain of logically connected operations 
{ 
    auto lockedHandle = threadSafeVector.ManuallyLock(); 
    if(!lockedHandle->empty()) 
    { 
     lockedHandle->pop_back(); 
     lockedHandle->push_back(-3); 
    } 
} 

for(auto& t : threads) 
{ 
    t.join(); 
} 

// And finally access the underlying object in a raw fashion without a lock 
// Use with Caution! 

std::vector<int>& rawVector = threadSafeVector.GetThreadUnsafeAccess(); 
rawVector.push_back(555); 

// Should be 16393 (5+3+16*1024+1) 
std::cout << threadSafeVector->size() << std::endl; 
11

Primero haga la clase de monitor genérico. Con el poder de C++ 11 puede hacerlo tan simple como esto:

template <class F> 
struct FunctionType; 
template <class R, class Object, class... Args> 
struct FunctionType<R (Object::*)(Args...)> { 
    typedef R return_type; 
}; 
template <class R, class Object, class... Args> 
struct FunctionType<R (Object::*)(Args...) const> { 
    typedef R return_type; 
}; 

template <class Object_> 
class Monitor { 
public: 
    typedef Object_ object_type; 
    template <class F, class... Args > 
    typename FunctionType<F>::return_type operation(const F& f, Args... args) 
    { 
     critical_section cs; 
     return (object.*f)(args...); 
    } 
    template <class F, class... Args > 
    typename FunctionType<F>::return_type operation(const F& f, Args... args) const 
    { 
     critical_section cs; 
     return (object.*f)(args...); 
    } 
private: 
    object_type object; 
    class critical_section {}; 
}; 

Por supuesto critical_section aplicación depende de usted. Recomiendo POSIX o algunos BOOST.

Está listo para utilizar ahora:

Monitor<std::vector<int> > v; 
v.operation((void (std::vector<int>::*)(const int&)) &std::vector<int>::push_back, 1); 
v.operation((void (std::vector<int>::*)(const int&)) &std::vector<int>::push_back, 2); 
size = v.operation(&std::vector<int>::size); 
std::cout << size << std::endl; 

Como se puede ver en ocasiones tendrá que indicar explícitamente qué función miembro que desea llamar - std :: vector <> tiene más de un push_back ...


para los compiladores que aún no son compatibles con la plantilla variadic - la solución sin que a continuación - tengo tiempo para un máximo de dos argumentos - que es muy incómodo - si es necesario - añadir función con más argumentos:

template <class F> 
struct FunctionType; 
template <class R, class Object> 
struct FunctionType<R (Object::*)()> { 
    typedef R return_type; 
}; 
template <class R, class Object> 
struct FunctionType<R (Object::*)() const> { 
    typedef R return_type; 
}; 
template <class R, class Object, class Arg1> 
struct FunctionType<R (Object::*)(Arg1)> { 
    typedef R return_type; 
}; 
template <class R, class Object, class Arg1> 
struct FunctionType<R (Object::*)(Arg1) const> { 
    typedef R return_type; 
}; 
template <class R, class Object, class Arg1, class Arg2> 
struct FunctionType<R (Object::*)(Arg1,Arg2)> { 
    typedef R return_type; 
}; 
template <class R, class Object, class Arg1, class Arg2> 
struct FunctionType<R (Object::*)(Arg1,Arg2) const> { 
    typedef R return_type; 
}; 

template <class Object_> 
class Monitor { 
public: 
    typedef Object_ object_type; 
    template <class F> 
    typename FunctionType<F>::return_type operation(const F& f) 
    { 
     critical_section cs; 
     return (object.*f)(); 
    } 
    template <class F> 
    typename FunctionType<F>::return_type operation(const F& f) const 
    { 
     critical_section cs; 
     return (object.*f)(); 
    } 
    template <class F, class Arg1> 
    typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1) 
    { 
     critical_section cs; 
     return (object.*f)(arg1); 
    } 
    template <class F, class Arg1> 
    typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1) const 
    { 
     critical_section cs; 
     return (object.*f)(arg1); 
    } 
    template <class F, class Arg1, class Arg2> 
    typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1, Arg2 arg2) 
    { 
     critical_section cs; 
     return (object.*f)(arg1, arg2); 
    } 
    template <class F, class Arg1, class Arg2> 
    typename FunctionType<F>::return_type operation(const F& f, Arg1 arg1, Arg2 arg2) const 
    { 
     critical_section cs; 
     return (object.*f)(arg1, arg2); 
    } 
private: 
    object_type object; 
    class critical_section {}; 
}; 
+0

Esta es una solución muy elegante; el único inconveniente es que requiere soporte de plantillas variadic, que todavía no tiene VC++. –

+1

@dario_ramos Érase una vez C++ no tenía plantillas variadic;) Puede utilizar métodos reemplazados: 'operación' con argumentos 0,1,2,3 ... en lugar de una' operación' con cualquier cantidad de argumentos. Suponiendo que nadie usa más de 10 argumentos, es posible implementarlo, aunque sea más problemático que las plantillas variadic ... – PiotrNycz

+0

Esa es una solución muy clara. Supongo que no puedes recomendar un libro o artículo que pueda explicar un poco más el uso de las plantillas variadic. Encuentro el struct FunctionType difícil de comprender (particularmente el bit Object :: *).) –

Cuestiones relacionadas