lock_guard c++ multithreading c++11 mutex

c++ - lock_guard - mutex c#



C++ 11: ¿por qué std:: condition_variable utiliza std:: unique_lock? (2)

Estoy un poco confundido sobre el papel de std::unique_lock cuando se trabaja con std::condition_variable . Por lo que he entendido la documentation , std::unique_lock es básicamente un blooded lock guard, con la posibilidad de cambiar el estado entre dos bloqueos.

Hasta ahora he usado pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) para este propósito (supongo que eso es lo que STL usa en posix). Se necesita un mutex, no un bloqueo.

¿Cuál es la diferencia aquí? ¿El hecho de que std::condition_variable trate con std::unique_lock una optimización? Si es así, ¿cómo es exactamente más rápido?


entonces no hay una razón técnica?

Volví a votar la respuesta de cmeerw porque creo que dio una razón técnica. Vamos a caminar a través de eso. Supongamos que el comité decidió tener condition_variable wait en un mutex . Aquí está el código que usa ese diseño:

void foo() { mut.lock(); // mut locked by this thread here while (not_ready) cv.wait(mut); // mut locked by this thread here mut.unlock(); }

Así es exactamente como uno no debería usar una condition_variable . En las regiones marcadas con:

// mut locked by this thread here

hay un problema de seguridad de excepción, y es serio. Si se lanza una excepción en estas áreas (o por cv.wait ), el estado bloqueado del mutex se filtra a menos que también se coloque un try / catch en algún lugar para atrapar la excepción y desbloquearla. Pero eso es solo más código que le pides al programador que escriba.

Digamos que el programador sabe cómo escribir código de excepción de seguridad, y sabe usar unique_lock para lograrlo. Ahora el código se ve así:

void foo() { unique_lock<mutex> lk(mut); // mut locked by this thread here while (not_ready) cv.wait(*lk.mutex()); // mut locked by this thread here }

Esto es mucho mejor, pero todavía no es una gran situación. La interfaz condition_variable hace que el programador se salga de su camino para que las cosas funcionen. Existe una posible desreferencia del puntero nulo si lk accidentalmente no hace referencia a un mutex. Y no hay forma de que condition_variable::wait compruebe que este hilo posee el bloqueo en mut .

Ah, acabo de recordar, también existe el peligro de que el programador elija la función del miembro unique_lock incorrecto para exponer el mutex. *lk.release() sería desastroso aquí.

Ahora veamos cómo se escribe el código con la API condition_variable actual que toma un unique_lock<mutex> :

void foo() { unique_lock<mutex> lk(mut); // mut locked by this thread here while (not_ready) cv.wait(lk); // mut locked by this thread here }

  1. Este código es tan simple como puede ser.
  2. Es una excepción segura.
  3. La función de wait puede verificar lk.owns_lock() y lanzar una excepción si es false .

Estas son razones técnicas que impulsaron el diseño API de condition_variable .

Además, condition_variable::wait no toma un lock_guard<mutex> porque lock_guard<mutex> es como dice: Tengo el bloqueo de este mutex hasta que lock_guard<mutex> destruye. Pero cuando llama a condition_variable::wait , libera implícitamente el bloqueo en el mutex. Entonces, esa acción es inconsistente con el caso / declaración de uso lock_guard .

Necesitábamos unique_lock todos modos para que uno pudiera devolver bloqueos de funciones, ponerlos en contenedores y bloquear / desbloquear mutexes en patrones sin ámbito de una manera segura, por lo que unique_lock era la elección natural para condition_variable::wait .

Actualizar

Bamboon sugirió en los comentarios a continuación que contrastar condition_variable_any , así que aquí va:

Pregunta: ¿Por qué no condition_variable::wait templated para poder pasarle cualquier tipo de Lockable ?

Responder:

Esa es una funcionalidad realmente genial de tener. Por ejemplo, este documento muestra código que espera en un shared_lock (rwlock) en modo compartido en una variable de condición (algo inaudito en el mundo de posix, pero muy útil, no obstante). Sin embargo, la funcionalidad es más costosa.

Entonces, el comité introdujo un nuevo tipo con esta funcionalidad:

`condition_variable_any`

Con este adaptador condition_variable uno puede esperar en cualquier tipo bloqueable. Si tiene miembros de lock() y unlock() , está listo para ir. Una implementación adecuada de condition_variable_any requiere un miembro de datos condition_variable y un miembro de datos shared_ptr<mutex> .

Debido a que esta nueva funcionalidad es más costosa que su condition_variable::wait y porque condition_variable es una herramienta de bajo nivel, esta funcionalidad muy útil pero más costosa se colocó en una clase separada para que solo pague si la usa.


Básicamente, se trata de una decisión de diseño de la API para que la API sea lo más segura posible de forma predeterminada (con la sobrecarga adicional como insignificante). Al requerir pasar un unique_lock lugar de un mutex sin mutex usuarios de la API se dirigen a escribir el código correcto (en presencia de excepciones).

En los últimos años, el enfoque del lenguaje C ++ se ha desplazado hacia la seguridad por defecto (pero aún permite a los usuarios pegarse un tiro si quieren y lo intentan lo suficiente).