Hinzufügen einer Anmerkung zum Sperrverhalten
Um Parallelitätsfehler in Ihrem Multithread-Programm zu vermeiden, befolgen Sie stets ein entsprechendes Sperrverhalten, und verwenden Sie SAL-Anmerkungen.
Parallelitätsfehler sind immer sehr schwer zu reproduzieren, zu diagnostizieren und zu debuggen, da sie sich nicht deterministisch verhalten. Thread Interleaving ist immer ein schwieriges Thema, was ganz besonders gilt, wenn Sie Code mit mehr als nur einigen wenigen Threads entwerfen. Daher ist es sehr sinnvoll, in Ihren Multithread-Programmen ein strenges Sperrverhalten zu befolgen. Wenn Sie z. B. eine Sperrreihenfolge einhalten, wenn mehrere Sperren verwendet werden, vermeiden Sie damit Deadlocks, und das Abrufen der korrekten Schutzsperre vor dem Zugriff auf eine freigegebene Ressource hilft dabei, „Race Conditions“ zu vermeiden.
Leider können scheinbar einfache Sperrregeln oft überraschend schwer zu befolgen sein. Eine grundlegende Einschränkung in modernen Programmiersprachen und Compilern besteht darin, dass sie die Spezifikation und Analyse der Parallelitätsanforderungen nicht direkt unterstützen. Programmierer müssen sich auf informelle Codekommentare stützen, um ihre Intentionen hinsichtlich der Verwendung von Sperren auszudrücken.
SAL-Anmerkungen zur Parallelität sind so konzipiert, dass Sie Nebenwirkungen von Sperren, die Sperrverantwortung, die Datenbewahrung, die Hierarchie der Sperrreihenfolge und andere erwartete Sperrverhaltensweisen angeben können. Dadurch, dass sie implizite Regeln explizit machen, bieten SAL-Anmerkungen zur Parallelität eine konsistente Möglichkeit, zu dokumentieren, wie Ihr Code Sperrregeln verwendet. Anmerkungen zur Parallelität verbessern auch die Fähigkeit von Codeanalysetools zum Auffinden von „Race Conditions“, nicht übereinstimmenden Synchronisierungsvorgängen und anderen subtilen Parallelitätsfehlern.
Allgemeine Richtlinien
Mithilfe von Anmerkungen können Sie die Vereinbarungen angeben, die durch Funktionsdefinitionen zwischen Implementierungen (Callees) und Clients (Callers) impliziert werden. Sie können auch Invarianten und andere Eigenschaften des Programms ausdrücken, die die Analyse weiter verbessern können.
SAL unterstützt viele verschiedene Arten von Sperrgrundtypen, z. B. kritische Abschnitte, Mutexes, Spin Locks und andere Ressourcenobjekte. Viele Anmerkungen zur Parallelität verwenden einen Sperrausdruck als Parameter. Gemäß Konvention wird eine Sperre durch den Pfadausdruck des zugrunde liegenden Sperrobjekts gekennzeichnet.
Einige Regeln zur Thread-Eigentümerschaft, die berücksichtigt werden sollten:
Spin Locks sind nicht gezählte Sperren, für die eine klare Thread-Eigentümerschaft gilt.
Mutexes und kritische Abschnitte sind gezählte Sperren, für die eine klare Thread-Eigentümerschaft gilt.
Semaphoren und Ereignisse sind gezählte Sperren, für die keine klare Thread-Eigentümerschaft gilt.
Sperren von Anmerkungen
In der folgenden Tabelle sind die Sperranmerkungen aufgeführt.
Anmerkung | Beschreibung |
---|---|
_Acquires_exclusive_lock_(expr) |
Annotiert eine Funktion und gibt an, dass die Funktion im Post-Zustand die Zahl der exklusiven Sperren des Sperrobjekts, das von expr benannt wird, um eins erhöht. |
_Acquires_lock_(expr) |
Annotiert eine Funktion und gibt an, dass die Funktion im Post-Zustand die Zahl der Sperren des Sperrobjekts, das von expr benannt wird, um eins erhöht. |
_Acquires_nonreentrant_lock_(expr) |
Die von expr benannte Sperre wird abgerufen. Wenn die Sperre bereits gehalten wird, wird ein Fehler gemeldet. |
_Acquires_shared_lock_(expr) |
Annotiert eine Funktion und gibt an, dass die Funktion im Post-Zustand die Zahl der gemeinsam genutzten Sperren des Sperrobjekts, das von expr benannt wird, um eins erhöht. |
_Create_lock_level_(name) |
Eine Anweisung, die das Symbol name als Sperrebene deklariert, so dass es in den Anmerkungen _Has_Lock_level_ und _Lock_level_order_ verwendet werden kann. |
_Has_lock_kind_(kind) |
Annotiert alle Objekte, um die Typinformationen eines Ressourcenobjekts zu verfeinern. Manchmal wird ein allgemeiner Typ für verschiedene Arten von Ressourcen verwendet, und der überladene Typ reicht nicht aus, um die semantischen Anforderungen zwischen verschiedenen Ressourcen zu differenzieren. Hier ist eine Liste vordefinierter kind -Parameter:_Lock_kind_mutex_ Sperrtyp-ID für Mutexes. _Lock_kind_event_ Sperrtyp-ID für Ereignisse. _Lock_kind_semaphore_ Sperrtyp-ID für Semaphoren. _Lock_kind_spin_lock_ Sperrtyp-ID für Spin Locks. _Lock_kind_critical_section_ Sperrtyp-ID für kritische Abschnitte. |
_Has_lock_level_(name) |
Annotiert ein Sperrobjekt und weist diesem die Sperrebene name zu. |
_Lock_level_order_(name1, name2) |
Eine Anweisung, die die Sperrreihenfolge zwischen name1 und name2 angibt. Sperren mit Ebene name1 müssen vor Sperren mit Ebene name2 abgerufen werden. |
_Post_same_lock_(dst, src) |
Annotiert eine Funktion und gibt an, dass die beiden Sperren, dst und src im Post-Zustand behandelt werden, als wären sie dasselbe Sperrobjekt; dazu werden Sperreigenschaften von src bis dst angewendet. |
_Releases_exclusive_lock_(expr) |
Annotiert eine Funktion und gibt an, dass die Funktion im Post-Zustand die Zahl der exklusiven Sperren des Sperrobjekts, das von expr benannt wird, um eins verringert. |
_Releases_lock_(expr) |
Annotiert eine Funktion und gibt an, dass die Funktion im Post-Zustand die Zahl der Sperren des Sperrobjekts, das von expr benannt wird, um eins verringert. |
_Releases_nonreentrant_lock_(expr) |
Die von expr benannte Sperre wird freigegeben. Wenn die Sperre derzeit nicht gehalten wird, wird ein Fehler gemeldet. |
_Releases_shared_lock_(expr) |
Annotiert eine Funktion und gibt an, dass die Funktion im Post-Zustand die Zahl der gemeinsam genutzten Sperren des Sperrobjekts, das von expr benannt wird, um eins verringert. |
_Requires_lock_held_(expr) |
Annotiert eine Funktion und gibt an, dass im Pre-Zustand die Anzahl der Sperren des Objekts, das von expr benannt wird, mindestens eins beträgt. |
_Requires_lock_not_held_(expr) |
Annotiert eine Funktion und gibt an, dass im Pre-Zustand die Anzahl der Sperren des Objekts, das von expr benannt wird, null beträgt. |
_Requires_no_locks_held_ |
Annotiert eine Funktion und gibt an, dass die Anzahl aller Sperren, die dem Checker bekannt sind, null ist. |
_Requires_shared_lock_held_(expr) |
Annotiert eine Funktion und gibt an, dass im Pre-Zustand die Zahl der gemeinsam genutzten Sperren des Objekts, das von expr benannt wird, mindestens eins beträgt. |
_Requires_exclusive_lock_held_(expr) |
Annotiert eine Funktion und gibt an, dass im Pre-Zustand die Zahl der exklusiven Sperren des Objekts, das von expr benannt wird, mindestens eins beträgt. |
Systeminterne SAL-Funktionen für nicht verfügbare Sperrobjekte
Bestimmte Sperrobjekte werden durch die Implementierung der zugehörigen Sperrfunktionen nicht verfügbar gemacht. In der folgenden Tabelle sind systeminterne SAL-Variablen aufgeführt, die Anmerkungen für Funktionen ermöglichen, die auf diesen nicht exponierten Sperrobjekten operieren.
Anmerkung | Beschreibung |
---|---|
_Global_cancel_spin_lock_ |
Beschreibt die Cancel Spin Lock. |
_Global_critical_region_ |
Beschreibt die kritische Region. |
_Global_interlock_ |
Beschreibt Interlocked-Vorgänge. |
_Global_priority_region_ |
Beschreibt die Prioritätsregion. |
Anmerkungen zum freigegebenen Datenzugriff
In der folgenden Tabelle sind die Anmerkungen für den Zugriff auf gemeinsam genutzte Daten aufgeführt.
Anmerkung | Beschreibung |
---|---|
_Guarded_by_(expr) |
Annotiert eine Variable und gibt an, dass bei jedem Zugriff auf die Variable die Anzahl der Sperren des von expr benannten Sperrobjekts mindestens eins beträgt. |
_Interlocked_ |
Annotiert eine Variable und entspricht _Guarded_by_(_Global_interlock_) . |
_Interlocked_operand_ |
Der annotierte Funktionsparameter ist der Ziel-Operand einer der verschiedenen Interlocked-Funktionen. Solche Operanden müssen andere spezifische Eigenschaften aufweisen. |
_Write_guarded_by_(expr) |
Annotiert eine Variable und gibt an, dass bei jeder Änderung der Variable die Anzahl der Sperren des von expr benannten Sperrobjekts mindestens eins beträgt. |
Smart Lock und RAII-Anmerkungen
Smart Locks umschließen in der Regel systemeigene Sperren und verwalten ihre Lebensdauer. In der folgenden Tabelle sind Anmerkungen aufgeführt, die mit Smart Locks und RAII-Codierungsmustern (Resource Acquisition Is Initialization) mit Unterstützung für die move
-Semantik verwendet werden können.
Anmerkung | Beschreibung |
---|---|
_Analysis_assume_smart_lock_acquired_(lock) |
Teilt dem Analysetool mit, dass davon anzugehen ist, dass ein Smart Lock abgerufen wurde. Diese Anmerkung erwartet einen Referenz-Sperrtyp als Parameter. |
_Analysis_assume_smart_lock_released_(lock) |
Teilt dem Analysetool mit, dass davon anzugehen ist, dass ein Smart Lock freigegeben wurde. Diese Anmerkung erwartet einen Referenz-Sperrtyp als Parameter. |
_Moves_lock_(target, source) |
Beschreibt einen move constructor -Vorgang, der den Sperrstatus vom source -Objekt zum target überträgt. Das target -Objekt wird als neu konstruiertes Objekt betrachtet, so dass jeder Zustand, den es vorher hatte, verloren geht und durch den source -Zustand ersetzt wird. Das source wird auch auf einen sauberen Zustand ohne Sperrenanzahl oder Aliasing-Ziel zurückgesetzt, darauf verweisende Aliase bleiben jedoch unverändert. |
_Replaces_lock_(target, source) |
Beschreibt die move assignment operator -Semantik, bei der die Zielsperre freigegeben wird, bevor der Zustand von der Quelle übertragen wird. Sie können dies als eine Kombination von _Moves_lock_(target, source) mit Voranstellung eines _Releases_lock_(target) betrachten. |
_Swaps_locks_(left, right) |
Beschreibt das Standardverhalten swap , bei dem davon ausgegangen wird, dass die Objekte left und right ihren Zustand austauschen. Der ausgetauschte Zustand beinhaltet die Anzahl der Sperren und ein eventuell vorhandenes Aliasing-Ziel. Aliase, die auf die left - und right -Objekte verweisen, bleiben unverändert. |
_Detaches_lock_(detached, lock) |
Beschreibt ein Szenario, in dem ein Sperr-Wrappertyp die Dissoziation von der enthaltenen Ressource zulässt. Dies ist ähnlich der Funktionsweise von std::unique_ptr mit seinem internen Pointer: Programmierer können den Pointer extragieren und den intelligenten Pointer-Container in einem sauberen Zustand belassen. Eine ähnliche Logik wird von std::unique_lock unterstützt und kann in benutzerdefinierten Sperr-Wrappern implementiert werden. Die getrennte Sperre behält ihren Zustand bei (Sperrenanzahl und Aliasing-Ziel, falls vorhanden), während der Wrapper so zurückgesetzt wird, dass er die Anzahl der Sperren und kein Aliasing-Ziel enthält, aber seine eigenen Aliase behält. Es erfolgt keine Operation für die Anzahl der Sperren (Freigabe und Abruf). Diese Anmerkung verhält sich genau so wie _Moves_lock_ , mit der Ausnahme, dass das getrennte Argument return und nicht this sein sollte. |
Siehe auch
- Verwenden von SAL-Anmerkungen zum Reduzieren von C/C++-Codefehlern
- Einführung in SAL
- Hinzufügen einer Anmerkung zu Funktionsparametern und Rückgabewerten
- Hinzufügen einer Anmerkung zum Funktionsverhalten
- Hinzufügen einer Anmerkung zu Strukturen und Klassen
- Angeben, wann und wo eine Anmerkung gültig ist
- Systeminterne Funktionen
- Empfohlene Vorgehensweisen und Beispiele