c++ - ¿Los mutexes deben ser mutables?
(2)
La pregunta oculta es: ¿dónde colocas el mutex para proteger tu clase?
Como resumen, digamos que desea leer el contenido de un objeto que está protegido por un mutex.
El método de "lectura" debe ser semánticamente "const" porque no cambia el objeto en sí. Pero para leer el valor, debe bloquear un mutex, extraer el valor y luego desbloquear el mutex, lo que significa que el mutex mismo debe modificarse, lo que significa que el mutex en sí no puede ser "const".
Si el mutex es externo
Entonces todo está bien. El objeto puede ser "const", y el mutex no necesita ser:
Mutex mutex ;
int foo(const Object & object)
{
Lock<Mutex> lock(mutex) ;
return object.read() ;
}
En mi humilde opinión, esta es una mala solución, porque cualquiera podría reutilizar el mutex para proteger a otra persona. Incluyéndote. De hecho, te traicionarás a ti mismo porque, si tu código es lo suficientemente complejo, te confundirás sobre lo que este u otro mutex protege exactamente.
Lo sé: fui víctima de ese problema.
Si el mutex es interno
Para fines de encapsulación, debe colocar el mutex lo más cerca posible del objeto que está protegiendo.
Generalmente, escribirás una clase con un mutex adentro. Pero tarde o temprano, tendrás que proteger alguna estructura compleja de STL, o cualquier cosa escrita por otra sin mutex adentro (lo cual es algo bueno).
Una buena forma de hacerlo es derivar el objeto original con una plantilla heredada que agrega la función mutex:
template <typename T>
class Mutexed : public T
{
public :
Mutexed() : T() {}
// etc.
void lock() { this->m_mutex.lock() ; }
void unlock() { this->m_mutex.unlock() ; } ;
private :
Mutex m_mutex ;
}
De esta manera, puedes escribir:
int foo(const Mutexed<Object> & object)
{
Lock<Mutexed<Object> > lock(object) ;
return object.read() ;
}
El problema es que no funcionará porque el object
es const, y el objeto de bloqueo está llamando a los métodos de lock
y unlock
sin const.
El dilema
Si crees que const
está limitado a los objetos de const bitwise, entonces estás jodido, y debes volver a la "solución mutex externa".
La solución es admitir que const
es más un calificador semántico (que es volatile
cuando se usa como un método calificador de clases). Está ocultando el hecho de que la clase no es totalmente const
pero asegúrese de proporcionar una implementación que mantenga la promesa de que las partes significativas de la clase no se modificarán cuando se llame a un método const
.
A continuación, debe declarar su mutex mutable, y los métodos de bloqueo / desbloqueo const
:
template <typename T>
class Mutexed : public T
{
public :
Mutexed() : T() {}
// etc.
void lock() const { this->m_mutex.lock() ; }
void unlock() const { this->m_mutex.unlock() ; } ;
private :
mutable Mutex m_mutex ;
}
La solución mutex interna es una buena en mi humilde opinión: tener objetos declarados uno cerca del otro en una mano, y tener ambos agregados en una envoltura en la otra mano, es lo mismo al final.
Pero la agregación tiene los siguientes pros:
- Es más natural (bloquea el objeto antes de acceder a él)
- Un objeto, un mutex. Como el estilo del código lo obliga a seguir este patrón, disminuye los riesgos de interbloqueo porque un mutex protegerá solo un objeto (y no múltiples objetos que no recordará realmente), y un objeto estará protegido por un solo mutex (y no por mutex múltiple que debe bloquearse en el orden correcto)
- La clase mutexed anterior se puede utilizar para cualquier clase
Por lo tanto, mantenga su mutex lo más cerca posible del objeto mutexed (por ejemplo, utilizando el constructo Mutexed anterior), y vaya para el calificador mutable
para el mutex.
Editar 2013-01-04
Aparentemente, Herb Sutter tiene el mismo punto de vista: su presentación sobre los "nuevos" significados de const
y mutable
en C ++ 11 es muy esclarecedora:
http://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/
No estoy seguro si esta es una pregunta de estilo, o algo que tiene una regla difícil ...
Si quiero mantener la interfaz de método público como const como sea posible, pero hacer que el hilo de objeto sea seguro, ¿debería usar mutex mutables? En general, ¿es este buen estilo, o debería preferirse una interfaz de método no const? Por favor, justifique su vista.
[ Respuesta editada ]
Básicamente, usar métodos const con mutex mutables es una buena idea (no devuelva referencias por cierto, asegúrese de devolver por valor), al menos para indicar que no modifican el objeto. Los mutexes no deberían ser const, sería una mentira descarada definir los métodos de bloqueo / desbloqueo como const ...
En realidad, esto (y la memorización) son los únicos usos justos que veo de la palabra clave mutable
.
También podría usar un mutex que sea externo a su objeto: haga arreglos para que todos sus métodos sean reentrantes, y { lock locker(the_mutex); obj.foo(); }
que el usuario administre el bloqueo ella misma: { lock locker(the_mutex); obj.foo(); }
{ lock locker(the_mutex); obj.foo(); }
{ lock locker(the_mutex); obj.foo(); }
no es tan difícil de escribir, y
{
lock locker(the_mutex);
obj.foo();
obj.bar(42);
...
}
tiene la ventaja de que no requiere dos bloqueos de mutex (y se garantiza que el estado del objeto no cambió).