c++ - pthread - std:: unique_lock<std:: mutex> o std:: lock_guard<std:: mutex>?
mutex c++ (5)
Tengo dos casos de uso.
A. Quiero sincronizar el acceso de dos hilos a una cola.
B. Quiero sincronizar el acceso de dos subprocesos a una cola y usar una variable de condición porque uno de los subprocesos esperará a que el otro subproceso guarde el contenido en la cola.
Para el caso de uso AI, vea el ejemplo de código usando std::lock_guard<>
. Para el caso de uso BI vea el ejemplo de código usando std::unique_lock<>
.
¿Cuál es la diferencia entre los dos y cuál debo usar en qué caso de uso?
Como lo han mencionado otros, std :: unique_lock rastrea el estado bloqueado del mutex, por lo que puede aplazar el bloqueo hasta después de la construcción del bloqueo, y desbloquearlo antes de destruirlo. std :: lock_guard no permite esto.
No parece haber ninguna razón por la que las funciones de espera std :: condition_variable deban tomar tanto lock_guard como unique_lock, porque cada vez que finaliza la espera (por cualquier motivo) el mutex se vuelve a adquirir automáticamente, por lo que no causaría ninguna violación semántica. Sin embargo, de acuerdo con el estándar, para usar un std :: lock_guard con una variable de condición, debe usar un std :: condition_variable_any en lugar de std :: condition_variable.
Edición : eliminado "Usar la interfaz pthreads std :: condition_variable y std :: condition_variable_any debe ser idéntico". Mirando la implementación de gcc:
- std :: condition_variable :: wait (std :: unique_lock &) simplemente llama a pthread_cond_wait () en la variable de condición pthread subyacente con respecto al mutex que posee unique_lock (y así podría hacer lo mismo para lock_guard, pero no porque el estándar no provee para eso
- std :: condition_variable_any puede trabajar con cualquier objeto bloqueable, incluido uno que no sea un bloqueo de exclusión mutua (por lo tanto, incluso podría funcionar con un semáforo entre procesos)
Hay ciertas cosas comunes entre lock_guard
y unique_lock
y ciertas diferencias.
Pero en el contexto de la pregunta formulada, el compilador no permite usar un lock_guard
en combinación con una variable de condición, porque cuando una llamada de un hilo espera en una variable de condición, el mutex se desbloquea automáticamente y cuando otro hilo / hilos se notifica y el actual el hilo se invoca (sale de la espera), el bloqueo se vuelve a adquirir.
Este fenómeno va en contra del principio de lock_guard
. lock_guard
puede construirse solo una vez y destruirse solo una vez.
Por lo tanto, lock_guard
no se puede usar en combinación con una variable de condición, pero un unique_lock
puede serlo (porque unique_lock
puede ser bloqueado y desbloqueado varias veces).
La diferencia es que puedes bloquear y desbloquear un std::unique_lock
. std::lock_guard
solo se bloqueará una vez en la construcción y se desbloqueará en la destrucción.
Así que para el caso de uso B definitivamente necesita un std::unique_lock
para la variable de condición. En el caso A depende si necesitas volver a bloquear la guardia.
std::unique_lock
tiene otras características que le permiten, por ejemplo: construirse sin bloquear el mutex inmediatamente pero construir el contenedor RAII (ver here ).
std::lock_guard
también proporciona un envoltorio RAII conveniente, pero no puede bloquear múltiples mutex de forma segura. Puede usarse cuando necesite un contenedor para un alcance limitado, por ejemplo: una función miembro:
class MyClass{
std::mutex my_mutex;
void member_foo() {
std::lock_guard<mutex_type> lock(this->my_mutex);
/*
block of code which needs mutual exclusion (e.g. open the same
file in multiple threads).
*/
//mutex is automatically released when lock goes out of scope
};
Para aclarar una pregunta por chmike, por defecto std::lock_guard
y std::unique_lock
son lo mismo. Entonces, en el caso anterior, puede reemplazar std::lock_guard
con std::unique_lock
. Sin embargo, std::unique_lock
puede tener un poco más de sobrecarga.
Tenga en cuenta que en estos días uno debe usar std::scoped_lock
lugar de std::lock_guard
.
Use lock_guard
menos que necesite poder unlock
manualmente la exclusión mutua intermedia sin destruir el lock
.
En particular, condition_variable
desbloquea su exclusión mutua cuando se va a dormir tras las llamadas para wait
. Es por eso que un lock_guard
no es suficiente aquí.
lock_guard
y unique_lock
son casi lo mismo; lock_guard
es una versión restringida con una interfaz limitada.
Un lock_guard
siempre mantiene un bloqueo desde su construcción hasta su destrucción. Se puede crear un unique_lock
sin bloqueo inmediato, se puede desbloquear en cualquier momento de su existencia y puede transferir la propiedad del bloqueo de una instancia a otra.
Por lo tanto, siempre utiliza lock_guard
, a menos que necesite las capacidades de unique_lock
. Una condition_variable
necesita un unique_lock
.