std :: lock_guard o std :: scoped_lock?

143

C ++ 17 introdujo una nueva clase de bloqueo llamada std::scoped_lock.

A juzgar por la documentación, se parece a la std::lock_guardclase ya existente .

¿Cuál es la diferencia y cuándo debo usarla?

Stephan Dollberg
fuente

Respuestas:

125

Esta scoped_lockes una versión estrictamente superior lock_guardque 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_guardaú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).

Kerrek SB
fuente
8
Además, gracias a la deducción de argumentos de plantilla de clase, ni siquiera tiene que enumerar los tipos bloqueables.
Nicol Bolas
3
@NicolBolas: Eso es cierto, pero eso también se aplica lock_guard. Pero ciertamente hace que las clases de guardia sean un poco más fáciles de usar.
Kerrek SB
66
scoped_lock es solo C ++ 17
Shital Shah
1
Como es c ++ 17, la compatibilidad es una razón particularmente buena para su existencia. También estoy en total desacuerdo con cualquier afirmación absolutista de "solo deberías usar" cuando la tinta todavía se está secando de este estándar.
Paul Childs
88

La única e importante diferencia es que std::scoped_locktiene 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::lockse 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::lockcomo 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_guarddesuso. El caso de argumento único de std::scoped_lockpuede implementarse como una especialización y no tendrá que temer sobre posibles problemas de rendimiento.

GCC 7 ya tiene soporte para lo std::scoped_lockque se puede ver aquí .

Para obtener más información, puede leer el documento estándar.

Stephan Dollberg
fuente
9
Respondí tu propia pregunta después de solo 10 min. ¿Realmente no lo sabías?
Walter
23
@Walter Hice stackoverflow.blog/2011/07/01/…
Stephan Dollberg el
3
Cuando lo mencioné en el comité, la respuesta fue "nada". Puede ser que el caso degenerado de algún algoritmo, esto sea exactamente lo correcto. O puede ser que suficientes personas accidentalmente no bloqueen nada cuando pretenden bloquear algo es un problema común. Realmente no estoy seguro.
Howard Hinnant
3
@HowardHinnant: scoped_lock lk; // locks all mutexes in scope. LGTM.
Kerrek SB
2
@KerrekSB: scoped_lock lk;es la nueva taquigrafía para scoped_lock<> lk;. No hay ninguna mutex. Entonces tienes razón. ;-)
Howard Hinnant
26

Respuesta tardía, y principalmente en respuesta a:

Puede considerar en std::lock_guarddesuso.

Para el caso común de que uno necesita bloquear exactamente un mutex, std::lock_guardtiene 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_guardse 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:

  1. lock_guard si necesita bloquear exactamente 1 mutex para un alcance completo.

  2. scoped_lock si necesita bloquear una cantidad de mutexes que no es exactamente 1.

  3. unique_locksi necesita desbloquear dentro del alcance del bloque (que incluye el uso con a condition_variable).

Este consejo no no implica que scoped_lockdebe ser rediseñado para que no acepte 0 mutex. Existen casos de uso válidos en los que es deseable scoped_lockaceptar 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_guardque 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).

Howard Hinnant
fuente
13

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_locksignifica que la mayoría de los casos en los que habría utilizado std::lockantes de c ++ 17 ahora se pueden escribir utilizando std::scoped_lock, con menos potencial de errores, ¡lo cual solo puede ser algo bueno!

陳 力
fuente