Annotering av låsningsbeteende

För att undvika samtidighetsbuggar i ditt flertrådade program bör du alltid följa en lämplig låsningsdisciplin och använda SAL-annoteringar.

Samtidighetsbuggar är notoriskt svåra att återskapa, diagnostisera och felsöka eftersom de är icke-terministiska. Att resonera kring trådinterfoliering är i bästa fall svårt och blir opraktiskt när du utformar en kodtext som har mer än några trådar. Därför är det bra att följa en låsedisciplin i dina flertrådade program. Till exempel, att följa en låsordning när du skaffar flera lås hjälper till att undvika dödlägen, och att skaffa rätt skyddande lås innan du får åtkomst till en delad resurs hjälper till att förhindra tävlingstillstånd.

Tyvärr kan till synes enkla låsregler vara förvånansvärt svåra att följa i praktiken. En grundläggande begränsning i dagens programmeringsspråk och kompilatorer är att de inte har direkt stöd för specifikation och analys av samtidighetskrav. Programmerare måste förlita sig på informella kodkommenterar för att uttrycka sina avsikter om hur de använder lås.

SAL-anteckningar för samtidighet är utformade för att hjälpa dig att specificera låsningsbiverkningar, ansvar för låsning, dataförvaltning, låsordningshierarki och annat förväntat låsbeteende. Genom att göra implicita regler explicita ger SAL-samtidighetsanteckningar ett konsekvent sätt för dig att dokumentera hur koden använder låsregler. Samtidighetsanteckningar förbättrar också möjligheten för kodanalysverktyg att hitta konkurrensförhållanden, dödlägen, felmatchade synkroniseringsåtgärder och andra subtila samtidighetsfel.

Allmänna riktlinjer

Med hjälp av annotationer kan du ange kontrakt som är underförstådda av funktionsdefinitioner mellan implementationer (callees) och klienter (anropare). Du kan också uttrycka invarianter och andra egenskaper för programmet som kan förbättra analysen ytterligare.

SAL har stöd för många olika typer av låsprimitiver, till exempel kritiska avsnitt, mutexar, spinnlås och andra resursobjekt. Många samtidighetsanteckningar använder ett låsuttryck som parameter. Enligt konventionen anges ett lås av sökvägsuttrycket för det underliggande låsobjektet.

Några regler för trådägarskap att tänka på:

  • Spinnlås är oräknade lås som har tydligt trådägarskap.

  • Mutexes och kritiska avsnitt är räknelås som har tydligt trådat ägarskap.

  • Semaforer och händelser är räknade lås som saknar tydligt trådägarskap.

Låsa annoteringar

I följande tabell visas låsningsanteckningarna.

Anteckning Beskrivning
_Acquires_exclusive_lock_(expr) Kommenterar en funktion och anger att när funktionen har körts ökar den antalet exklusiva lås med ett för det låsobjekt som namnges av expr.
_Acquires_lock_(expr) Kommenterar en funktion och anger att i eftertillstånd ökar funktionen med ett låsantal för låsobjektet som namnges av expr.
_Acquires_nonreentrant_lock_(expr) Låset som namnges av expr har förvärvats. Ett fel rapporteras om låset redan finns kvar.
_Acquires_shared_lock_(expr) Kommenterar en funktion och anger att i eftertillstånd ökar funktionen med ett delat låsantal för det låsobjekt som namnges av expr.
_Create_lock_level_(name) En instruktion som deklarerar symbolen name som en låsnivå så att den kan användas i anteckningarna _Has_Lock_level_ och _Lock_level_order_.
_Has_lock_kind_(kind) Kommenterar alla objekt för att förfina typinformationen för ett resursobjekt. Ibland används en vanlig typ för olika typer av resurser och den överlagrade typen räcker inte för att särskilja semantiska krav mellan olika resurser. Här är en lista över fördefinierade kind parametrar:

_Lock_kind_mutex_
Lås typ-ID för mutexer.

_Lock_kind_event_
Lås typ-ID för händelser.

_Lock_kind_semaphore_
Låsningstyp-ID för semaforer.

_Lock_kind_spin_lock_
Lås typ-ID för spinnlås.

_Lock_kind_critical_section_
Lås typ-ID för kritiska avsnitt.
_Has_lock_level_(name) Kommenterar ett låsobjekt och ger det låsnivån name.
_Lock_level_order_(name1, name2) Ett uttalande som anger låsordningen mellan name1 och name2. Lås som har nivå name1 ska förvärvas innan lås som har nivå name2.
_Post_same_lock_(dst, src) Kommenterar en funktion och anger att i eftertillstånd behandlas de två låsen och dstsrc, som om de vore samma låsobjekt, genom att tillämpa låsegenskaper från src till dst.
_Releases_exclusive_lock_(expr) Kommenterar en funktion och anger att funktionen i efterstadiet minskar med ett (1) det exklusiva låsantalet för låsobjektet som namnges av expr.
_Releases_lock_(expr) Kommenterar en funktion och anger att i efterläge minskar funktionen låsantalet med ett för det låsobjekt som namnges av expr.
_Releases_nonreentrant_lock_(expr) Låset som heter expr har släppts. Ett fel rapporteras om låset inte hålls för närvarande.
_Releases_shared_lock_(expr) Annoterar en funktion och anger att i efterläge minskar funktionen det delade låsantalet med ett för det låsobjekt som anges av expr.
_Requires_lock_held_(expr) Kommenterar en funktion och anger att låsantalet för objektet som namnges av expr är minst ett i förtillstånd.
_Requires_lock_not_held_(expr) Kommenterar en funktion och anger att i förtillstånd är låsantalet för objektet som namnges av expr noll.
_Requires_no_locks_held_ Kommenterar en funktion och anger att låsantalet för alla lås som är kända för kontrollen är noll.
_Requires_shared_lock_held_(expr) Kommenterar en funktion och anger att det delade låsantalet för objektet som namnges av expr är minst ett i förhandstillstånd.
_Requires_exclusive_lock_held_(expr) Kommenterar en funktion och anger att det exklusiva låsantalet för objektet som namnges av expr är minst ett i förhandstillstånd.

SAL-intrinsik för oexponerade låsobjekt

Vissa låsobjekt exponeras inte av implementeringen av de associerade låsfunktionerna. I följande tabell visas inbyggda SAL-variabler som aktiverar anteckningar för funktioner som fungerar på de oexponerade låsobjekten.

Anteckning Beskrivning
_Global_cancel_spin_lock_ Beskriver avbrytningsspinnlåset.
_Global_critical_region_ Beskriver den kritiska regionen.
_Global_interlock_ Beskriver sammankopplade åtgärder.
_Global_priority_region_ Beskriver prioritetsregionen.

Anteckningar om delad dataåtkomst

I följande tabell visas anteckningarna för delad dataåtkomst.

Anteckning Beskrivning
_Guarded_by_(expr) Kommenterar en variabel och anger att när variabeln används är låsantalet för låsobjektet som namnges av expr minst ett.
_Interlocked_ Kommenterar en variabel och motsvarar _Guarded_by_(_Global_interlock_).
_Interlocked_operand_ Den kommenterade funktionsparametern är måloperand för en av de olika sammankopplade funktionerna. Dessa operander måste ha andra specifika egenskaper.
_Write_guarded_by_(expr) Kommenterar en variabel och anger att när variabeln ändras är låsantalet för låsobjektet som namnges av expr minst ett.

Smart Lock- och RAII-anteckningar

Smarta lås omsluter vanligtvis interna lås och hanterar deras livslängd. I följande tabell visas anteckningar som kan användas med smarta lås och kodningsmönster för resource acquisition is initialization (RAII) med stöd för move semantik.

Anteckning Beskrivning
_Analysis_assume_smart_lock_acquired_(lock) Uppmanar analysatorn att anta att ett smart lås har förvärvats. Den här kommentaren förväntar sig en referenslåstyp som parameter.
_Analysis_assume_smart_lock_released_(lock) Uppmanar analysatorn att anta att ett smart lås har släppts. Den här kommentaren förväntar sig en referenslåstyp som parameter.
_Moves_lock_(target, source) Beskriver en move constructor åtgärd som överför låstillståndet source från objektet till target. target Anses vara ett nyligen konstruerat objekt, så alla tillstånd som det hade tidigare går förlorade och ersätts av tillståndetsource. source återställs också till ett rent tillstånd utan låsräknare eller aliasmål, men alias som pekar på det förblir oförändrade.
_Replaces_lock_(target, source) Beskriver move assignment operator-semantiken där mållåset frigörs, innan tillståndet överförs från källan. Du kan betrakta det som en kombination av _Moves_lock_(target, source) föregås av en _Releases_lock_(target).
_Swaps_locks_(left, right) Beskriver standardbeteendet swap , som förutsätter att objekt left och right utbyter deras tillstånd. Tillståndet som utbyts inkluderar låsantal och aliaseringsmål, om det finns. Alias som pekar på objekten left och right förblir oförändrade.
_Detaches_lock_(detached, lock) Beskriver ett scenario där en låsomslagstyp tillåter dissociering med dess inneslutna resurs. Det liknar hur std::unique_ptr fungerar med sin interna pekare: det tillåter programmerare att extrahera pekaren och lämna smartpekarkontainern i ett rent tillstånd. Liknande logik stöds av std::unique_lock och kan implementeras i anpassade låsomslutningar. Det frånkopplade låset behåller sitt tillstånd (låsantal och aliasmål, om det finns något), medan omslutningen återställs till att innehålla noll låsantal och inget aliasmål, samtidigt som dess egna alias behålls. Det finns ingen åtgärd för antal lås (frigör och hämtar). Den här kommentaren beter sig exakt som _Moves_lock_ förutom att det frånkopplade argumentet ska vara return i stället för this.

Se även