Visão geral dos primitivos de sincronização

O .NET fornece uma variedade de tipos que você pode usar para sincronizar o acesso a um recurso compartilhado ou coordenar a interação de thread.

Importante

Use a mesma instância de primitivos de sincronização para proteger o acesso de um recurso compartilhado. Se usar instâncias primitivas de sincronização diferentes para proteger o mesmo recurso, você evitará a proteção fornecida por um primitivo de sincronização.

Tipos de sincronização leve e a classe WaitHandle

Vários primitivos de sincronização .NET derivam da classe System.Threading.WaitHandle, que encapsula um identificador de sincronização do sistema operacional nativo e usa um mecanismo de sinalização de interação de thread. Essas classes incluem:

  • System.Threading.Mutex, que concede acesso exclusivo a um recurso compartilhado. O estado de um mutex será sinalizado se nenhum thread for seu proprietário.
  • System.Threading.Semaphore, que limita o número de threads que podem acessar um recurso compartilhado ou um pool de recursos simultaneamente. O estado de um semáforo é definido como sinalizado quando a contagem é maior que zero e não sinalizado quando a contagem é zero.
  • System.Threading.EventWaitHandle, que representa um evento de sincronização de thread e pode estar em um estado sinalizado ou não sinalizado.
  • System.Threading.AutoResetEvent, que é derivado de EventWaitHandle e, quando sinalizado, é redefinido automaticamente para um estado não sinalizado após o lançamento de um único thread em espera.
  • System.Threading.ManualResetEvent, que é derivado de EventWaitHandle e, quando sinalizado, permanece no estado sinalizado até que o método Reset seja chamado.

No .NET Framework, porque WaitHandle deriva de System.MarshalByRefObject, esses tipos podem ser usados para sincronizar as atividades de threads entre limites de domínio de aplicativo.

No .NET Framework, no .NET Core e no .NET 5+, alguns desses tipos podem representar os identificadores de sincronização de sistema nomeado, que são visíveis em todo o sistema operacional e podem ser usados para a sincronização entre processos:

Para obter mais informações, veja a referência de API WaitHandle.

Tipos de sincronização leve não dependem de identificadores do sistema operacional subjacente e normalmente fornecem um melhor desempenho. No entanto, não podem ser usados para a sincronização entre processos. Use esses tipos para sincronização de thread dentro de um aplicativo.

Alguns desses tipos são alternativas aos tipos derivados de WaitHandle. Por exemplo, SemaphoreSlim é uma alternativa leve para Semaphore.

Sincronização de acesso a um recurso compartilhado

O .NET fornece uma variedade de primitivos de sincronização para controlar o acesso a um recurso compartilhado por vários threads.

Classe Monitor

A classe System.Threading.Monitor concede acesso mutuamente exclusivo em um recurso compartilhado, adquirindo ou liberando um bloqueio no objeto que identifica o recurso. Embora um bloqueio seja mantido, o thread que mantém o bloqueio pode adquiri-lo novamente e liberá-lo. Qualquer outro thread é impedido de adquirir o bloqueio e o método Monitor.Enter aguardará até que o bloqueio seja liberado. O método Enter adquire um bloqueio liberado. Você também pode usar o método Monitor.TryEnter para especificar o tempo durante o qual um thread tenta adquirir um bloqueio. Porque a classe Monitor tem afinidade de thread, o thread que adquiriu um bloqueio deve liberar o bloqueio chamando o método Monitor.Exit.

Você pode coordenar a interação de threads que adquirirem um bloqueio no mesmo objeto usando os métodos Monitor.Wait, Monitor.Pulse e Monitor.PulseAll.

Para obter mais informações, veja a referência de API Monitor.

Observação

Use a instrução lock no C# e a instrução SyncLock no Visual Basic para sincronizar o acesso a um recurso compartilhado, em vez de usar a classe Monitor diretamente. Essas instruções são implementadas usando os métodos Enter e Exit e um bloco try…finally para garantir que o bloqueio adquirido sempre seja liberado.

Classe Mutex

A classe System.Threading.Mutex, como Monitor, concede acesso exclusivo a um recurso compartilhado. Use uma das sobrecargas do método Mutex.WaitOne para solicitar a propriedade de um mutex. Como Monitor, Mutex tem afinidade de thread e o thread que adquiriu um mutex deverá liberá-lo chamando o método Mutex.ReleaseMutex.

Diferente de Monitor, a classe Mutex pode ser usada para sincronização entre processos. Para fazer isso, use um mutex nomeado, que fica visível em todo o sistema operacional. Para criar uma instância de mutex nomeada, use um construtor Mutex que especifique um nome. Você também pode chamar o método Mutex.OpenExisting para abrir um mutex do sistema nomeado existente.

Para obter mais informações, veja o artigo Mutexes e a referência da API Mutex.

Estrutura de SpinLock

A estrutura System.Threading.SpinLock, como Monitor, concede acesso exclusivo a um recurso compartilhado com base na disponibilidade de um bloqueio. Quando SpinLock tenta adquirir um bloqueio que não está disponível, ele aguarda em um loop, verificando repetidamente até o bloqueio ficar disponível.

Para obter mais informações sobre as vantagens e as desvantagens do uso do bloqueio de rotação, veja o artigo SpinLock e a referência da API SpinLock.

Classe ReaderWriterLockSlim

A classe System.Threading.ReaderWriterLockSlim concede acesso exclusivo a um recurso compartilhado para gravação e permite que vários threads acessem o recurso simultaneamente para leitura. Você talvez queira usar ReaderWriterLockSlim para sincronizar o acesso a uma estrutura de dados compartilhada que dá suporte a operações de leitura thread-safe, mas requer acesso exclusivo para realizar a operação de gravação. Quando um thread solicita acesso exclusivo (por exemplo, ao chamar o método ReaderWriterLockSlim.EnterWriteLock), o leitor subsequente solicita o bloqueio até que todos os leitores e gravadores existentes tenham saído do bloqueio e o gravador tenha entrado e saído do bloqueio.

Para obter mais informações, veja a referência de API ReaderWriterLockSlim.

Classes Semaphore e SemaphoreSlim

As classes System.Threading.Semaphore e System.Threading.SemaphoreSlim limitam o número de threads que podem acessar um recurso compartilhado ou um pool de recursos simultaneamente. Threads adicionais que solicitam o recurso aguardam até que qualquer thread liberar o semáforo. Como o semáforo não tem afinidade de thread, um thread pode adquirir o semáforo e outro pode liberá-lo.

SemaphoreSlim é uma alternativa leve a Semaphore e pode ser usado somente para sincronização dentro do limite de um único processo.

No Windows, você pode usar Semaphore para a sincronização entre processos. Para fazer isso, crie uma instância Semaphore que representa um semáforo de sistema nomeado usando um dos Construtores de semáforo que especifica um nome ou o método Semaphore.OpenExisting. SemaphoreSlim não dá suporte a sinais de sistema nomeado.

Para obter mais informações, veja o artigo Semaphore e SemaphoreSlim e a referência à API Semaphore ou SemaphoreSlim.

Interação ou sinalização de thread

Interação de thread (ou sinalização do thread) significa que um thread deve aguardar notificação ou um sinal de um ou mais threads para continuar. Por exemplo, se o thread A chamar o método Thread.Join do thread B, o thread A será bloqueado até que o thread B seja concluído. Os primitivos de sincronização descritos na seção anterior fornecem um mecanismo diferente para sinalização: ao liberar um bloqueio, um thread notifica outro thread de que ele pode continuar adquirindo o bloqueio.

Esta seção descreve outras construções de sinalização fornecidas pelo .NET.

Classes EventWaitHandle, AutoResetEvent, ManualResetEvent e ManualResetEventSlim

A classe System.Threading.EventWaitHandle representa um evento de sincronização de thread.

Um evento de sincronização pode estar em um estado não sinalizado ou sinalizado. Quando o estado de um evento não é sinalizado, um thread que chama a sobrecarga WaitOne do evento é bloqueado até que um evento seja sinalizado. O método EventWaitHandle.Set define o estado de um evento como sinalizado.

O comportamento de um EventWaitHandle que foi assinalado depende de seu modo de redefinição:

No Windows, você pode usar EventWaitHandle para a sincronização entre processos. Par isso, crie uma instância EventWaitHandle que representa um evento de sincronização do sistema nomeado usando um dos constructos EventWaitHandle que especificam um nome ou o método EventWaitHandle.OpenExisting.

Para obter mais informações, confira o artigo EventWaitHandle. Para a referência de API, veja EventWaitHandle, AutoResetEvent, ManualResetEvent e ManualResetEventSlim.

Classe CountdownEvent

A classe System.Threading.CountdownEvent representa um evento definido quando a contagem é zero. Embora CountdownEvent.CurrentCount seja maior que zero, um thread que chama CountdownEvent.Wait é bloqueado. Chame CountdownEvent.Signal para diminuir a contagem de um evento.

Em contraste com ManualResetEvent ou ManualResetEventSlim, que você pode usar para desbloquear vários threads com um sinal de um thread, você pode usar CountdownEvent para desbloquear um ou mais threads com sinais de vários threads.

Para obter mais informações, veja o artigo CountdownEvent e a referência à API CountdownEvent.

Classe de barreira

A classe System.Threading.Barrier representa uma barreira de execução do thread. Um thread que chama o método Barrier.SignalAndWait sinaliza que atingiu a barreira e aguarda até que outros threads participantes atinjam a barreira. Quando todos os threads participantes atingem a barreira, eles prosseguem e a barreira é redefinida e pode ser usada novamente.

Você pode usar Barrier quando um ou mais threads exigirem os resultados de outros threads antes de prosseguir para a próxima fase de cálculo.

Para obter mais informações, veja o artigo Barreira e a referência da API Barrier.

Classe Interlocked

A classe System.Threading.Interlocked fornece métodos estáticos que executam operações atômicas simples em uma variável. Essas operações atômicas incluem adição, incremento e decremento, troca e troca condicional que depende de uma comparação e operação de leitura de um valor inteiro de 64 bits.

Para obter mais informações, veja a referência de API Interlocked.

Estrutura SpinWait

A estrutura System.Threading.SpinWait oferece suporte para espera baseada em rotação. Você pode usá-la quando um thread tiver de esperar pela sinalização de um evento ou por uma condição específica. No entanto, quando o tempo de espera real for menor do que o tempo necessário, use um identificador de espera ou bloqueie o thread. Usando o SpinWait, você pode especificar um curto período de tempo para girar enquanto espera e, em seguida, gerar (por exemplo, aguardando ou em espera) somente se a condição não for atendida no tempo especificado.

Para obter mais informações, veja o artigo SpinWait e a referência da API SpinWait.

Confira também