70 votes

C++11 : pourquoi std::condition_variable utilise-t-il std::unique_lock ?

Je suis un peu confus quant au rôle de std::unique_lock lorsque vous travaillez avec std::condition_variable . D'après ce que j'ai compris, le documentation , std::unique_lock est fondamentalement une garde de verrou bloquée, avec la possibilité d'échanger l'état entre deux verrous.

J'ai utilisé jusqu'à présent pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) dans ce but (je suppose que c'est ce que la STL utilise sous Posix). Il prend un mutex, pas un verrou.

Quelle est la différence ici ? Est-ce que le fait que std::condition_variable traite std::unique_lock une optimisation ? Si oui, en quoi est-elle plus rapide ?

101voto

Howard Hinnant Points 59526

Il n'y a donc pas de raison technique ?

J'ai upvoted la réponse de cmeerw parce que je crois qu'il a donné une raison technique. Faisons le tour de la question. Imaginons que la commission ait décidé d'avoir condition_variable attendre sur un mutex . Voici un code utilisant ce modèle :

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

C'est exactement comme ça qu'on ne devrait pas utiliser un condition_variable . Dans les régions marquées par :

// mut locked by this thread here

il y a un problème de sécurité exceptionnel, et c'est un problème sérieux. Si une exception est lancée dans ces zones (ou par cv.wait ), l'état verrouillé du mutex est divulgué à moins qu'un try/catch ne soit également placé quelque part pour attraper l'exception et le déverrouiller. Mais ce n'est que du code supplémentaire que vous demandez au programmeur d'écrire.

Disons que le programmeur sait comment écrire du code sécurisé par les exceptions, et sait utiliser unique_lock pour y parvenir. Maintenant, le code ressemble à ceci :

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
}

C'est beaucoup mieux, mais ce n'est toujours pas une situation idéale. Le site condition_variable oblige le programmeur à faire des pieds et des mains pour que les choses fonctionnent. Il y a un déréférencement possible du pointeur nul si lk ne fait pas accidentellement référence à un mutex. Et il n'y a aucun moyen pour condition_variable::wait pour vérifier que ce fil possède bien le verrou sur mut .

Oh, je viens de me rappeler qu'il y a aussi le risque que le programmeur choisisse le mauvais unique_lock pour exposer le mutex. *lk.release() serait désastreux ici.

Maintenant, regardons comment le code est écrit avec l'actuel condition_variable qui prend 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. Ce code est aussi simple qu'il peut l'être.
  2. Il s'agit d'une sécurité exceptionnelle.
  3. Le site wait peut vérifier lk.owns_lock() et lancer une exception si c'est le cas false .

Ce sont des raisons techniques qui ont motivé la conception de l'API de l'UE. condition_variable .

En outre, condition_variable::wait ne prend pas de lock_guard<mutex> parce que lock_guard<mutex> c'est comment on dit : Je possède le verrou sur ce mutex jusqu'à ce que lock_guard<mutex> détruit. Mais lorsque vous appelez condition_variable::wait vous libérez implicitement le verrou sur le mutex. Cette action est donc incompatible avec la lock_guard cas d'utilisation / déclaration.

Nous avions besoin unique_lock de toute façon, afin de pouvoir renvoyer des verrous à partir de fonctions, les placer dans des conteneurs et verrouiller/déverrouiller des mutex dans des schémas non scopés de manière sûre du point de vue des exceptions. unique_lock était le choix naturel pour condition_variable::wait .

Mise à jour

bamboon a suggéré dans les commentaires ci-dessous que je mette en contraste condition_variable_any alors voilà :

Question : Pourquoi n'est-ce pas condition_variable::wait afin que je puisse passer n'importe quel Lockable type à elle ?

Réponse :

C'est une fonctionnalité vraiment cool à avoir. Par exemple cet article démontre un code qui attend sur un shared_lock (rwlock) en mode partagé sur une variable de condition (quelque chose d'inédit dans le monde posix, mais néanmoins très utile). Cependant, cette fonctionnalité est plus coûteuse.

La commission a donc introduit un nouveau type avec cette fonctionnalité :

`condition_variable_any`

Avec cette condition_variable adaptateur on peut attendre tout type verrouillable. S'il a des membres lock() y unlock() vous êtes prêt à partir. Une mise en œuvre correcte de condition_variable_any nécessite un condition_variable et un élément de données shared_ptr<mutex> membre des données.

Parce que cette nouvelle fonctionnalité est plus onéreuse que votre service de base condition_variable::wait et parce que condition_variable est un outil de si bas niveau, cette fonctionnalité très utile mais plus coûteuse a été placée dans une classe séparée afin que vous ne payiez que si vous l'utilisez.

34voto

cmeerw Points 4412

Il s'agit essentiellement d'une décision de conception de l'API visant à rendre l'API aussi sûre que possible par défaut (la surcharge supplémentaire étant considérée comme négligeable). En exigeant de passer un unique_lock au lieu d'un mutex les utilisateurs de l'API sont orientés vers l'écriture d'un code correct (en présence d'exceptions).

Ces dernières années, le langage C++ s'est orienté vers la sécurisation par défaut (tout en permettant aux utilisateurs de se tirer dans les pattes s'ils le veulent et s'ils font suffisamment d'efforts).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X