C ++ 17 introdujo una nueva clase de bloqueo llamada std::scoped_lock
.
A juzgar por la documentación, se parece a la std::lock_guard
clase ya existente .
¿Cuál es la diferencia y cuándo debo usarla?
C ++ 17 introdujo una nueva clase de bloqueo llamada std::scoped_lock
.
A juzgar por la documentación, se parece a la std::lock_guard
clase ya existente .
¿Cuál es la diferencia y cuándo debo usarla?
Respuestas:
Esta scoped_lock
es una versión estrictamente superior lock_guard
que bloquea un número arbitrario de mutexes a la vez (usando el mismo algoritmo de evitación de bloqueo std::lock
). En el nuevo código, solo debes usarlo scoped_lock
.
La única razón que lock_guard
aún existe es por compatibilidad. No se puede eliminar simplemente porque se usa en el código actual. Además, resultó indeseable cambiar su definición (de unaria a variadica), porque también es un cambio observable y, por lo tanto, rompedor (pero por razones algo técnicas).
lock_guard
. Pero ciertamente hace que las clases de guardia sean un poco más fáciles de usar.
La única e importante diferencia es que std::scoped_lock
tiene un constructor variable que toma más de un mutex. Esto permite bloquear múltiples mutexes en un punto muerto evitando la forma como si std::lock
se usaran.
{
// safely locked as if using std::lock
std::scoped_lock<std::mutex, std::mutex> lock(mutex1, mutex2);
}
Anteriormente, tenía que bailar un poco para bloquear múltiples mutexes de manera segura utilizando std::lock
como se explica esta respuesta .
La adición del bloqueo de alcance hace que sea más fácil de usar y evita los errores relacionados. Puede considerar en std::lock_guard
desuso. El caso de argumento único de std::scoped_lock
puede implementarse como una especialización y no tendrá que temer sobre posibles problemas de rendimiento.
GCC 7 ya tiene soporte para lo std::scoped_lock
que se puede ver aquí .
Para obtener más información, puede leer el documento estándar.
scoped_lock lk; // locks all mutexes in scope
. LGTM.
scoped_lock lk;
es la nueva taquigrafía para scoped_lock<> lk;
. No hay ninguna mutex. Entonces tienes razón. ;-)
Respuesta tardía, y principalmente en respuesta a:
Puede considerar en
std::lock_guard
desuso.
Para el caso común de que uno necesita bloquear exactamente un mutex, std::lock_guard
tiene una API que es un poco más segura de usar que scoped_lock
.
Por ejemplo:
{
std::scoped_lock lock; // protect this block
...
}
Es probable que el fragmento anterior sea un error accidental en tiempo de ejecución porque se compila y luego no hace absolutamente nada. El codificador probablemente quiso decir:
{
std::scoped_lock lock{mut}; // protect this block
...
}
Ahora se bloquea / desbloquea mut
.
Si lock_guard
se utilizó en los dos ejemplos anteriores, el primer ejemplo es un error en tiempo de compilación en lugar de un error en tiempo de ejecución, y el segundo ejemplo tiene una funcionalidad idéntica a la versión que utiliza scoped_lock
.
Entonces, mi consejo es usar la herramienta más simple para el trabajo:
lock_guard
si necesita bloquear exactamente 1 mutex para un alcance completo.
scoped_lock
si necesita bloquear una cantidad de mutexes que no es exactamente 1.
unique_lock
si necesita desbloquear dentro del alcance del bloque (que incluye el uso con a condition_variable
).
Este consejo no no implica que scoped_lock
debe ser rediseñado para que no acepte 0 mutex. Existen casos de uso válidos en los que es deseable scoped_lock
aceptar paquetes de parámetros de plantilla variadic que pueden estar vacíos. Y la caja vacía no debe bloquear nada.
Y es por eso lock_guard
que no está en desuso. scoped_lock
y unique_lock
puede ser un superconjunto de funcionalidades de lock_guard
, pero ese hecho es una espada de doble filo. A veces es igual de importante lo que un tipo no hará (construcción predeterminada en este caso).
Aquí hay una muestra y una cita de C ++ Concurrency in Action :
friend void swap(X& lhs, X& rhs)
{
if (&lhs == & rhs)
return;
std::lock(lhs.m, rhs.m);
std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
swap(lhs.some_detail, rhs.some_detail);
}
vs.
friend void swap(X& lhs, X& rhs)
{
if (&lhs == &rhs)
return;
std::scoped_lock guard(lhs.m, rhs.m);
swap(lhs.some_detail, rhs.some_detail);
}
La existencia de
std::scoped_lock
significa que la mayoría de los casos en los que habría utilizadostd::lock
antes de c ++ 17 ahora se pueden escribir utilizandostd::scoped_lock
, con menos potencial de errores, ¡lo cual solo puede ser algo bueno!