Annotation du comportement de verrouillage
Pour éviter les bogues d'accès concurrentiel dans votre programme multithread, suivez toujours une règle de verrouillage appropriée et utilisez les annotations SAL.
Les bogues d’accès concurrentiel sont notoirement difficiles à reproduire, diagnostiquer et déboguer, car ils ne sont pas déterministes. Le raisonnement sur l’entrelacement des threads est difficile au mieux et devient impraticable lorsque vous concevez un corps de code qui a plus de quelques threads. Par conséquent, il est recommandé de suivre une discipline de verrouillage dans vos programmes multithread. Par exemple, obéir à un ordre de verrou tout en obtenant plusieurs verrous permet d'éviter des interblocages, et acquérir le verrou de garde approprié avant d'accéder à une ressource partagée permet d'éviter des conditions de concurrence.
Malheureusement, ces règles de verrouillage apparemment simples peuvent être étonnamment difficiles à suivre en pratique. Une limitation fondamentale dans les langages de programmation et les compilateurs actuels est qu’ils ne prennent pas directement en charge la spécification et l’analyse des exigences de concurrence. Les programmeurs doivent s'appuyer sur les commentaires de code informels pour exprimer leurs intentions sur la façon dont ils utilisent des verrous.
Les annotations SAL d'accès concurrentiel sont conçues pour vous aider à spécifier les effets secondaires du verrouillage, le responsable du verrouillage, la tutelle de données, la hiérarchie d'ordre de verrou et d'autres comportements de verrouillage prévus. En transformant des règles implicites en règles explicites, les annotations d'accès concurrence SAL vous permettent de documenter la façon dont votre code utilise des règles de verrouillage. Les annotations d'accès concurrentiel améliorent également la capacité des outils d'analyse du code à rechercher des conditions de concurrence, des interblocages, des opérations de synchronisation incompatibles et d'autres erreurs subtiles d'accès concurrentiel.
Instructions générales
En utilisant des annotations, vous pouvez indiquer les contrats qui sont implicites par les définitions de fonction entre les implémentations (appelés) et les clients (appelants). Vous pouvez également exprimer des invariants et d’autres propriétés du programme qui peuvent améliorer davantage l’analyse.
SAL prend en charge de nombreux types de primitives de verrouillage, tels que les sections critiques, les mutex, les verrous de rotation et d'autres objets de ressource. De nombreuses annotations d’accès concurrentiel prennent une expression de verrou en tant que paramètre. Par convention, un verrou est indiqué par l’expression de chemin d’accès de l’objet de verrou sous-jacent.
Quelques règles de propriété concernant les threads à garder en tête :
Les verrous de rotation sont des verrous non numérotés qui ont une propriété de thread précise.
Les mutex et les sections critiques sont des verrous numérotés qui ont une propriété de thread précise.
Les sémaphores et les événements sont comptabilisés des verrous qui n’ont pas de propriété de thread claire.
Verrouillage des annotations
Le tableau suivant répertorie les annotations de verrouillage.
Annotation | Description |
---|---|
_Acquires_exclusive_lock_(expr) |
Annote une fonction et indique qu’à l’état postérieur la fonction incrémente d’une unité le nombre de verrous exclusifs de l’objet verrou nommé par expr . |
_Acquires_lock_(expr) |
Annote une fonction et indique qu’à l’état postérieur la fonction incrémente d’une unité le nombre de verrous de l’objet verrou nommé par expr . |
_Acquires_nonreentrant_lock_(expr) |
Le verrou nommé par expr est acquis. Une erreur est signalée si le verrou est déjà détenu. |
_Acquires_shared_lock_(expr) |
Annote une fonction et indique qu'à l'état postérieur la fonction incrémente d'une unité le nombre de verrous partagés de l'objet verrou nommé par expr . |
_Create_lock_level_(name) |
Instruction qui déclare le symbole name comme un niveau de verrou afin qu'il puisse être utilisé dans les annotations _Has_Lock_level_ et _Lock_level_order_ . |
_Has_lock_kind_(kind) |
Annote n’importe quel objet pour affiner les informations de type d’un objet de ressource. Parfois, un type commun est utilisé pour différents types de ressources et le type surchargé n’est pas suffisant pour distinguer les exigences sémantiques entre différentes ressources. Voici la liste des paramètres kind prédéfinis :_Lock_kind_mutex_ ID de type de verrou pour les mutex. _Lock_kind_event_ ID de type de verrou pour les événements. _Lock_kind_semaphore_ ID de type de verrou pour les sémaphores. _Lock_kind_spin_lock_ ID de type de verrou pour les verrous de rotation. _Lock_kind_critical_section_ ID de type de verrou pour les sections critiques. |
_Has_lock_level_(name) |
Annote un objet verrou, en lui donnant le niveau de verrou de name . |
_Lock_level_order_(name1, name2) |
Instruction qui donne l’ordre de verrouillage entre name1 et name2 . Les verrous qui ont un niveau name1 doivent être acquis avant les verrous qui ont le niveau name2 . |
_Post_same_lock_(expr1, expr2) |
Annote une fonction et indique que, dans l’état post, expr1 les deux verrous et expr2 , sont traités comme s’ils sont le même objet de verrou. |
_Releases_exclusive_lock_(expr) |
Annote une fonction et indique qu'à l'état postérieur la fonction décrémente d'une unité le nombre de verrous exclusifs de l'objet verrou nommé par expr . |
_Releases_lock_(expr) |
Annote une fonction et indique qu’à l’état postérieur la fonction décrémente d’une unité le nombre de verrous de l’objet verrou nommé par expr . |
_Releases_nonreentrant_lock_(expr) |
Le verrou nommé par expr est libéré. Une erreur est signalée si le verrou n’est pas conservé. |
_Releases_shared_lock_(expr) |
Annote une fonction et indique qu'à l'état postérieur la fonction décrémente d'une unité le nombre de verrous partagés de l'objet verrou nommé par expr . |
_Requires_lock_held_(expr) |
Annote une fonction et indique qu'à l'état antérieur le nombre de verrous de l'objet nommé par expr est d'au moins un. |
_Requires_lock_not_held_(expr) |
Annote une fonction et indique qu'à l'état antérieur le nombre de verrous de l'objet nommé par expr est zéro. |
_Requires_no_locks_held_ |
Annote une fonction et indique que le nombre de verrous de tous les verrous connus par le vérificateur est égal à zéro. |
_Requires_shared_lock_held_(expr) |
Annote une fonction et indique qu'à l'état antérieur le nombre de verrous partagés de l'objet nommé par expr est d'au moins un. |
_Requires_exclusive_lock_held_(expr) |
Annote une fonction et indique qu'à l'état antérieur le nombre de verrous exclusifs de l'objet nommé par expr est d'au moins un. |
Intrinsèques SAL pour les objets de verrouillage non exposés
Certains objets de verrouillage ne sont pas exposés par l’implémentation des fonctions de verrouillage associées. Le tableau suivant répertorie les variables intrinsèques SAL qui autorisent les annotations sur les fonctions qui s'exécutent sur ces objets verrous non exposés.
Annotation | Description |
---|---|
_Global_cancel_spin_lock_ |
Décrit le verrou d'annulation de rotation. |
_Global_critical_region_ |
Décrit la zone critique. |
_Global_interlock_ |
Décrit les opérations verrouillées. |
_Global_priority_region_ |
Décrit la zone de priorité. |
Annotations d’accès aux données partagées
Le tableau suivant répertorie les annotations à utiliser avec l'accès aux données partagées.
Annotation | Description |
---|---|
_Guarded_by_(expr) |
Annote une variable et indique qu’à chaque accès à la variable, le nombre de verrous de l’objet verrou nommé par expr est d’au moins un. |
_Interlocked_ |
Annote une variable et équivaut à _Guarded_by_(_Global_interlock_) . |
_Interlocked_operand_ |
Le paramètre de fonction annoté est l’opérande cible de l’une des différentes fonctions interblocées. Ces opérandes doivent avoir des propriétés supplémentaires spécifiques. |
_Write_guarded_by_(expr) |
Annote une variable et indique qu'à chaque modification de la variable, le nombre de verrous de l'objet verrou nommé par expr est d'au moins un. |
Annotations SMART Lock et RAII
Les verrous intelligents encapsulent généralement les verrous natifs et gèrent leur durée de vie. Le tableau suivant répertorie les annotations qui peuvent être utilisées avec des verrous intelligents et des modèles de codage RAII avec prise en charge de move
la sémantique.
Annotation | Description |
---|---|
_Analysis_assume_smart_lock_acquired_(lock) |
Indique à l’analyseur de supposer qu’un verrou intelligent a été acquis. Cette annotation attend un type de verrou de référence comme paramètre. |
_Analysis_assume_smart_lock_released_(lock) |
Indique à l’analyseur de supposer qu’un verrou intelligent a été libéré. Cette annotation attend un type de verrou de référence comme paramètre. |
_Moves_lock_(target, source) |
Décrit une move constructor opération, qui transfère l’état de verrouillage de l’objet à l’objet source target . Il target est considéré comme un objet nouvellement construit, donc tout état qu’il avait avant est perdu et remplacé par l’état source . Il source est également réinitialisé à un état propre sans nombre de verrous ni cible d’alias, mais les alias pointant vers celui-ci restent inchangés. |
_Replaces_lock_(target, source) |
Décrit move assignment operator la sémantique où le verrou cible est libéré avant de transférer l’état de la source. Vous pouvez le considérer comme une combinaison de _Moves_lock_(target, source) précédé d’un _Releases_lock_(target) . |
_Swaps_locks_(left, right) |
Décrit le comportement standard swap , qui suppose que les objets left et right échangent leur état. L’état échangé inclut le nombre de verrous et la cible d’alias, le cas échéant. Les alias qui pointent vers les left objets restent right inchangés. |
_Detaches_lock_(detached, lock) |
Décrit un scénario dans lequel un type de wrapper de verrou autorise la dissociation avec sa ressource autonome. Il est similaire à l’utilisation std::unique_ptr de son pointeur interne : il permet aux programmeurs d’extraire le pointeur et de laisser son conteneur de pointeur intelligent dans un état propre. Une logique similaire est prise en charge std::unique_lock et peut être implémentée dans des wrappers de verrous personnalisés. Le verrou détaché conserve son état (nombre de verrous et cible d’alias, le cas échéant), tandis que le wrapper est réinitialisé pour contenir zéro nombre de verrous et aucune cible d’alias, tout en conservant ses propres alias. Il n’existe aucune opération sur le nombre de verrous (libération et acquisition). Cette annotation se comporte exactement comme _Moves_lock_ si l’argument détaché doit être return plutôt que this . |
Voir aussi
- Utilisation d’annotations SAL pour réduire les défauts du code C/C++
- Présentation de SAL
- Annotation des paramètres de fonction et des valeurs de retour
- Annotation du comportement d’une fonction
- Annotations des structs et des classes
- Spécification du moment et de l’endroit où une annotation s’applique
- Fonctions intrinsèques
- Bonnes pratiques et exemples
Commentaires
https://aka.ms/ContentUserFeedback.
Bientôt disponible : Tout au long de 2024, nous allons supprimer progressivement GitHub Issues comme mécanisme de commentaires pour le contenu et le remplacer par un nouveau système de commentaires. Pour plus d’informations, consultezEnvoyer et afficher des commentaires pour