std :: lock_guard ou std :: scoped_lock?

143

C ++ 17 a introduit une nouvelle classe de verrouillage appelée std::scoped_lock.

À en juger par la documentation, il ressemble à la std::lock_guardclasse déjà existante .

Quelle est la différence et quand dois-je l'utiliser?

Stéphan Dollberg
la source

Réponses:

125

Il scoped_locks'agit d'une version strictement supérieure de lock_guardqui verrouille un nombre arbitraire de mutex en même temps (en utilisant le même algorithme d'évitement de blocage que std::lock). Dans le nouveau code, vous ne devriez jamais utiliser scoped_lock.

La seule raison lock_guardexiste encore est la compatibilité. Il ne pouvait pas simplement être supprimé, car il est utilisé dans le code actuel. De plus, il s'est avéré indésirable de changer sa définition (de unaire à variadique), car c'est aussi un changement observable, et donc cassant (mais pour des raisons quelque peu techniques).

Kerrek SB
la source
8
De plus, grâce à la déduction des arguments de modèle de classe, vous n'avez même pas besoin de lister les types verrouillables.
Nicol Bolas
3
@NicolBolas: C'est vrai, mais cela s'applique également à lock_guard. Mais cela rend certainement les classes de garde un peu plus faciles à utiliser.
Kerrek SB
6
scoped_lock est C ++ 17 uniquement
Shital Shah
1
Comme il s'agit de c ++ 17, la compatibilité est une raison particulièrement bonne de son existence. Je suis également en désaccord avec toute affirmation absolutiste selon laquelle «vous ne devriez jamais utiliser» que lorsque l'encre sèche encore à partir de cette norme.
Paul Childs
88

La seule et importante différence est qu'un std::scoped_lockconstructeur variadique prend plus d'un mutex. Cela permet de verrouiller plusieurs mutex de manière à éviter les interblocages comme s'ils std::lockétaient utilisés.

{
    // safely locked as if using std::lock
    std::scoped_lock<std::mutex, std::mutex> lock(mutex1, mutex2);     
}

Auparavant, vous deviez faire une petite danse pour verrouiller plusieurs mutex de manière sûre en utilisant std::lockcomme expliqué cette réponse .

L'ajout du verrouillage de la portée facilite l'utilisation et évite les erreurs associées. Vous pouvez considérer comme std::lock_guardobsolète. Le cas d'argument unique de std::scoped_lockpeut être implémenté en tant que spécialisation et vous n'avez donc pas à craindre d'éventuels problèmes de performances.

GCC 7 a déjà un support pour std::scoped_locklequel peut être vu ici .

Pour plus d'informations, vous voudrez peut-être lire l' article standard

Stéphan Dollberg
la source
9
Répondu à votre propre question après seulement 10 min. Vous ne saviez vraiment pas?
Walter
23
@Walter j'ai fait stackoverflow.blog/2011/07/01/…
Stephan Dollberg
3
Quand j'ai soulevé la question en comité, la réponse était «rien». Il se peut que le cas dégénéré de certains algorithmes, soit exactement la bonne chose. Ou il se peut qu'un nombre suffisant de personnes ne verrouille rien par accident alors qu'elles avaient l'intention de verrouiller quelque chose est un problème courant. Je ne suis vraiment pas sûr.
Howard Hinnant
3
@HowardHinnant: scoped_lock lk; // locks all mutexes in scope. LGTM.
Kerrek SB
2
@KerrekSB: scoped_lock lk;est le nouveau raccourci pour scoped_lock<> lk;. Il n'y a pas de mutex. Alors tu as raison. ;-)
Howard Hinnant
26

Réponse tardive, et principalement en réponse à:

Vous pouvez considérer comme std::lock_guardobsolète.

Pour le cas courant où l'on a besoin de verrouiller exactement un mutex, std::lock_guarddispose d'une API qui est un peu plus sûre à utiliser que scoped_lock.

Par exemple:

{
   std::scoped_lock lock; // protect this block
   ...
}

L'extrait ci-dessus est probablement une erreur d'exécution accidentelle car il se compile et ne fait absolument rien. Le codeur voulait probablement dire:

{
   std::scoped_lock lock{mut}; // protect this block
   ...
}

Maintenant, il se verrouille / se déverrouille mut.

Si a lock_guardété utilisé dans les deux exemples ci-dessus à la place, le premier exemple est une erreur de compilation au lieu d'une erreur d'exécution, et le deuxième exemple a des fonctionnalités identiques à la version qui utilise scoped_lock.

Mon conseil est donc d'utiliser l'outil le plus simple pour le travail:

  1. lock_guard si vous avez besoin de verrouiller exactement 1 mutex pour une étendue entière.

  2. scoped_lock si vous avez besoin de verrouiller un nombre de mutex différent de 1.

  3. unique_locksi vous avez besoin de déverrouiller dans le cadre du bloc (qui comprend l'utilisation avec a condition_variable).

Ce conseil n'implique pas qu'il scoped_lockdevrait être repensé pour ne pas accepter 0 mutex. Il existe des cas d'utilisation valides où il est souhaitable scoped_lockd'accepter des packs de paramètres de modèles variadiques qui peuvent être vides. Et la valise vide ne doit rien verrouiller.

Et c'est pourquoi lock_guardn'est pas obsolète. scoped_lock et unique_lock peut être un sur-ensemble de fonctionnalités de lock_guard, mais ce fait est une arme à double tranchant. Parfois, ce qu'un type ne fera pas est tout aussi important (construction par défaut dans ce cas).

Howard Hinnant
la source
13

Voici un exemple et une citation de C ++ Concurrency en 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);
}

contre.

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);
}

L'existence de std::scoped_locksignifie que la plupart des cas où vous auriez utilisé std::lockavant c ++ 17 peuvent maintenant être écrits en utilisant std::scoped_lock, avec moins de risques d'erreurs, ce qui ne peut être qu'une bonne chose!

陳 力
la source