Partilhar via


Anotação do comportamento de bloqueio

Para evitar erros de concorrência no seu programa multithread, siga sempre práticas de bloqueio adequadas e utilize anotações SAL.

Os bugs de simultaneidade são notoriamente difíceis de reproduzir, diagnosticar e depurar porque não são determinísticos. O raciocínio sobre a intercalação de threads é, na melhor das hipóteses, difícil e torna-se impraticável quando se está a projetar um código que tem mais do que uns poucos threads. Portanto, é uma boa prática seguir uma disciplina de bloqueio nos seus programas multiencadeados. Por exemplo, obedecer a uma ordem de bloqueio ao adquirir várias fechaduras ajuda a evitar impasses, e adquirir a fechadura de proteção adequada antes de acessar um recurso compartilhado ajuda a evitar condições de corrida.

Infelizmente, regras de bloqueio aparentemente simples podem ser surpreendentemente difíceis de seguir na prática. Uma limitação fundamental nas linguagens de programação e compiladores atuais é que eles não suportam diretamente a especificação e análise de requisitos de simultaneidade. Os programadores têm que confiar em comentários de código informais para expressar suas intenções sobre como eles usam bloqueios.

As anotações SAL de simultaneidade são projetadas para ajudá-lo a especificar efeitos secundários relacionados com bloqueios, responsabilidade de bloqueio, tutela de dados, hierarquia da ordem de bloqueio e outros comportamentos de bloqueio esperados. Ao tornar as regras implícitas explícitas, as anotações de simultaneidade SAL fornecem uma maneira consistente de documentar como seu código usa regras de bloqueio. As anotações de concorrência também aumentam a capacidade das ferramentas de análise do código para encontrar condições de corrida, deadlocks, operações de sincronização incompatíveis e outros erros subtis de concorrência.

Orientações Gerais

Usando anotações, você pode declarar os contratos que estão implícitos por definições de função entre implementações (callees) e clientes (chamadores). Você também pode expressar invariantes e outras propriedades do programa que podem melhorar ainda mais a análise.

A SAL suporta muitos tipos diferentes de primitivos de bloqueio — por exemplo, seções críticas, mutexes, bloqueios de rotação e outros objetos de recurso. Muitas anotações de concorrência usam uma expressão de bloqueio como parâmetro. Por convenção, um bloqueio é indicado pela expressão de caminho do objeto de bloqueio subjacente.

Algumas regras de propriedade de threads a ter em mente:

  • Os bloqueios de rotação são bloqueios incontáveis que têm propriedade clara do fio.

  • Mutexes e seções críticas são bloqueios contados que têm propriedade clara de threads.

  • Semáforos e eventos são bloqueios contados que não têm propriedade clara do encadeamento.

Anotações de bloqueio

A tabela a seguir lista as anotações de bloqueio.

Anotação Descrição
_Acquires_exclusive_lock_(expr) Anota uma função e indica que, no estado post, a função aumenta em um a contagem de bloqueio exclusiva do objeto de bloqueio nomeado por expr.
_Acquires_lock_(expr) Anota uma função e indica que, no estado pós, a função incrementa em uma a contagem de bloqueios do objeto de bloqueio nomeado por expr.
_Acquires_nonreentrant_lock_(expr) O cadeado que é nomeado por expr é adquirido. Um erro será relatado se o bloqueio já estiver mantido.
_Acquires_shared_lock_(expr) Anota uma função e indica que, após a execução, a função aumenta em um a contagem de bloqueio compartilhado do objeto de bloqueio designado por expr.
_Create_lock_level_(name) Uma instrução que declara o símbolo name como um nível de bloqueio para que possa ser usado nas anotações _Has_Lock_level_ e _Lock_level_order_.
_Has_lock_kind_(kind) Anota qualquer objeto para refinar as informações de tipo de um objeto de recurso. Às vezes, um tipo comum é usado para diferentes tipos de recursos e o tipo sobrecarregado não é suficiente para distinguir os requisitos semânticos entre vários recursos. Aqui está uma lista de parâmetros predefinidos kind :

_Lock_kind_mutex_
ID de tipo de bloqueio para mutexes.

_Lock_kind_event_
Bloquear ID de tipo para eventos.

_Lock_kind_semaphore_
Identificação do tipo de bloqueio para semáforos.

_Lock_kind_spin_lock_
ID do tipo de bloqueio para bloqueios rotativos.

_Lock_kind_critical_section_
ID de tipo de bloqueio para seções críticas.
_Has_lock_level_(name) Anota um objeto de bloqueio e dá-lhe o nível de bloqueio de name.
_Lock_level_order_(name1, name2) Uma instrução que dá a ordem de bloqueio entre name1 e name2. As fechaduras que têm nível name1 devem ser adquiridas antes das fechaduras que têm nível name2.
_Post_same_lock_(dst, src) Anota uma função e indica que, no estado pós, os bloqueios dst e src são tratados como se fossem o mesmo objeto de bloqueio, ao aplicar propriedades de bloqueio de src para dst.
_Releases_exclusive_lock_(expr) Anota uma função e indica que, no estado pós-execução, a função diminui em uma unidade a contagem de bloqueio exclusiva do objeto de bloqueio nomeado por expr.
_Releases_lock_(expr) Anota uma função e indica que, no estado post, a função diminui em um a contagem de bloqueio do objeto de bloqueio nomeado por expr.
_Releases_nonreentrant_lock_(expr) O cadeado nomeado por expr é desbloqueado. Um erro será relatado se o bloqueio não estiver mantido no momento.
_Releases_shared_lock_(expr) Anota uma função e indica que, no estado post, a função diminui em um a contagem de bloqueio compartilhada do objeto de bloqueio nomeado por expr.
_Requires_lock_held_(expr) Anota uma função e indica que, no estado inicial, a contagem de bloqueio do objeto nomeado por expr é no mínimo um.
_Requires_lock_not_held_(expr) Anota uma função e indica que, no pré-estado, a contagem de bloqueio do objeto nomeado por expr é zero.
_Requires_no_locks_held_ Anota uma função e indica que as contagens de bloqueios de todas as fechaduras conhecidas pelo verificador são zero.
_Requires_shared_lock_held_(expr) Anota uma função e indica que, no estado prévio, a contagem de bloqueio compartilhado do objeto denominado expr é de no mínimo um.
_Requires_exclusive_lock_held_(expr) Anota uma função e indica que, no pré-estado, a contagem de bloqueio exclusiva do objeto nomeado por expr é pelo menos uma.

Intrínsecos SAL para objetos de bloqueio não expostos

Certos objetos de bloqueio não são expostos pela implementação das funções de bloqueio associadas. A tabela a seguir lista as variáveis intrínsecas SAL que permitem anotações em funções que operam nesses objetos de bloqueio não expostos.

Anotação Descrição
_Global_cancel_spin_lock_ Descreve o bloqueio de rotação cancelar.
_Global_critical_region_ Descreve a região crítica.
_Global_interlock_ Descreve operações interligadas.
_Global_priority_region_ Descreve a região prioritária.

Anotações de acesso a dados compartilhados

A tabela a seguir lista as anotações para acesso a dados compartilhados.

Anotação Descrição
_Guarded_by_(expr) Anota uma variável e indica que, sempre que a variável é acedida, a contagem de bloqueios do objeto de bloqueio nomeado por expr é de pelo menos um.
_Interlocked_ Anota uma variável e é equivalente a _Guarded_by_(_Global_interlock_).
_Interlocked_operand_ O parâmetro de uma função anotada é o operando alvo de uma das funções Interlocked. Esses operandos devem ter outras propriedades específicas.
_Write_guarded_by_(expr) Anota uma variável e indica que, sempre que a variável é modificada, a contagem de bloqueio do objeto de bloqueio nomeado por expr é pelo menos uma.

Anotações Smart Lock e RAII

As fechaduras inteligentes normalmente envolvem fechaduras nativas e gerenciam sua vida útil. A tabela a seguir lista as anotações que podem ser usadas com bloqueios inteligentes e padrões de codificação RAII (Resource Acquisition Is Initialization) com suporte para move semântica.

Anotação Descrição
_Analysis_assume_smart_lock_acquired_(lock) Indica ao analisador que se considera que uma fechadura inteligente foi adquirida. Esta anotação requere um tipo de bloqueio de referência como seu parâmetro.
_Analysis_assume_smart_lock_released_(lock) Diz ao analisador que pressuponha que um bloqueio inteligente foi desbloqueado. Esta anotação requere um tipo de bloqueio de referência como seu parâmetro.
_Moves_lock_(target, source) Descreve a operação move constructor, que transfere o estado de bloqueio do source objeto para o target. O target é considerado um objeto recém-construído, então qualquer estado que tinha antes é perdido e substituído pelo source estado. O source é também restaurado para um estado limpo, sem contagens de bloqueio ou alvo de aliasing, mas os aliases que apontam para ele permanecem inalterados.
_Replaces_lock_(target, source) Descreve move assignment operator a semântica em que o bloqueio de destino é liberado antes de transferir o estado da origem. Você pode considerá-lo como uma combinação de _Moves_lock_(target, source) precedido por um _Releases_lock_(target).
_Swaps_locks_(left, right) Descreve o comportamento padrão swap, que pressupõe que os objetos left e right trocam os seus estados. O estado trocado inclui a contagem de bloqueios e o destino de aliasagem, se presente. Os aliases que apontam para os left objetos e right permanecem inalterados.
_Detaches_lock_(detached, lock) Descreve um cenário no qual um tipo de envolvente de bloqueio permite a desassociação do seu recurso contido. É semelhante à forma como std::unique_ptr funciona com o seu ponteiro interno: isso permite que os programadores extraiam o ponteiro e deixem o seu conteiner de ponteiro inteligente num estado limpo. A lógica semelhante é suportada por std::unique_lock e pode ser implementada em wrappers de bloqueio personalizados. O bloqueio separado mantém o seu estado (contagem de bloqueio e alvo de aliasing, se existir), enquanto o wrapper é reajustado para conter uma contagem de bloqueio zero e nenhum alvo de aliasing, mantendo os seus próprios aliases. Não há operação em contagens de bloqueio (liberação e aquisição). Esta anotação se comporta exatamente como _Moves_lock_ exceto que o argumento destacado deve ser return em vez de this.

Ver também