Relógio

Resumo : neste tutorial, você aprenderá como usar a lockinstrução C# para evitar condições de corrida e garantir a segurança do thread quando vários threads acessam recursos compartilhados.

Condições da corrida

Vamos começar com um programa simples:

int counter = 0;
void Increase()
{
    for (int i = 0; i < 1000000; i++)
    {
        counter++;
    }
    Console.WriteLine("The counter is " + counter);
}

Task.Run(() => Increase());
Task.Run(() => Increase());

Console.ReadLine();Linguagem de código:  C#  ( cs )

Como funciona.

Primeiro, defina uma variável countere inicialize-a com zero.

Segundo, defina o Increase()método que adiciona um à countervariável 1 milhão de vezes e exibe o valor final do contador no console.

Terceiro, crie duas tarefas que sejam executadas em threads separados . Cada tarefa executa o Increase()método para aumentar a countervariável.

Como ambas as tarefas modificam a countervariável simultaneamente, há risco de condições de corrida e problemas de sincronização.

Quando o programa aumenta a variável counter, ele realiza três etapas:

  • Leia o valor da countervariável.
  • Aumente em um.
  • Escreva o novo valor de volta na countervariável.

Em outras palavras, o aumento da countervariável não é uma operação atômica.

Portanto, uma tarefa pode ler o valor de counterantes que a outra tarefa tenha concluído a atualização, resultando em um valor incorreto ou em uma atualização perdida.

Aqui está a saída do programa:

The counter is 1113815
The counter is 1515277Linguagem de código:  C#  ( cs )

Observe que você poderá ver countervalores diferentes ao executar o programa várias vezes devido às condições de corrida entre duas tarefas (ou threads).

É por isso que a lockdeclaração vem em socorro.

Introdução à instrução de bloqueio C#

A lockinstrução evita condições de corrida e garante a segurança do thread quando vários threads acessam a mesma variável compartilhada.

Para usar a lockinstrução você cria um novo objeto que serve como bloqueio, também conhecido como mutex. O mutex significa exclusão mútua.

lock(lockObject)
{
   // access the shared resources
}Linguagem de código:  C#  ( cs )

Quando um thread entra em um lockbloco, ele tentará adquirir o bloqueio no arquivo lockObject.

Se o bloqueio já foi adquirido por outro thread, o thread atual será bloqueado até que o bloqueio seja liberado.

Depois que o bloqueio é liberado, o thread atual pode adquiri-lo e executar o código no lockbloco que frequentemente lê ou grava os recursos compartilhados.

O programa a seguir demonstra como usar uma lockinstrução para evitar uma condição de corrida entre dois threads:

int counter = 0;

object lockCounter = new();
void Increase()
{
    for (int i = 0; i < 1000000; i++)
    {
        lock (lockCounter) 
        { 
            counter++; 
        }

    }
    Console.WriteLine("The counter is " + counter);
}

Task.Run(() => Increase());
Task.Run(() => Increase());

Console.ReadLine();Linguagem de código:  C#  ( cs )

Este programa é semelhante ao programa anterior, mas usa uma instrução lock para sincronizar o acesso à countervariável.

A lockinstrução garante que ambas as tarefas acessem a countervariável de forma mutuamente exclusiva.

Isso significa que o valor final do contador é previsível e sempre igual à soma dos incrementos realizados por ambas as tarefas, que é 2,000,000.

Aqui está a saída do programa:

The counter is 1992352
The counter is 2000000Linguagem de código:  C#  ( cs )

Observe que o valor final do contador é sempre 2,000,000. Entretanto, o valor imediato ( 1,992,352) pode variar porque o programa está sendo executado simultaneamente, com dois threads correndo para incrementar a variável do contador.

Práticas recomendadas de bloqueio C#

A seguir estão algumas práticas recomendadas ao usar a lockinstrução C#:

  • Mantenha o lockbloco o menor possível para minimizar o tempo que outros threads terão que esperar pelo bloqueio. Se um lockbloco demorar muito para ser executado, poderá causar contenção entre threads e reduzir o desempenho do aplicativo.
  • Evite bloqueios aninhados porque eles podem causar um conflito . Os deadlocks ocorrem quando vários threads tentam adquirir bloqueios em uma ordem diferente.
  • Use try...finallyblock para liberar o bloqueio corretamente quando ocorrer uma exceção no lockbloco. Além disso, garante que outros threads não sejam bloqueados indefinidamente.
  • Considere usar mecanismos de sincronização alternativos como SemaphoreSlime ReaderWriterLockSlimporque eles podem fornecer melhor desempenho e controle mais refinado sobre a simultaneidade.

Resumo

  • Use a lockinstrução C# para evitar condições de corrida e problemas de sincronização quando vários threads acessam os mesmos recursos compartilhados.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *