lock (sharedObj1)
{
   ...
   lock (sharedObj2)
   {
       ...
   }
}

Observe que a ordem dos bloqueios no método Thread2Work foi alterada para corresponder à ordem no Thread1Work. Primeiro, um bloqueio é adquirido no SharedObJ1, depois um bloqueio é adquirido no SharedObJ2.

Aqui está a versão revisada da listagem completa de código:


class DeadlockDemo
{
    private static readonly object sharedObj1 = new();
    private static readonly object sharedObj2 = new();
    public static void Execute()
    {
        Thread thread1 = new Thread(Thread1Work);
        Thread thread2 = new Thread(Thread2Work);
        thread1.Start();
        thread2.Start();
        thread1.Join();
        thread2.Join();
        Console.WriteLine("Finished execution.");
    }
    static void Thread1Work()
    {
        lock (sharedObj1)
        {
            Console.WriteLine("Thread 1 has acquired a shared resource 1. " +
                "It is now waiting for acquiring a lock on resource 2");
            Thread.Sleep(1000);
            lock (sharedObj2)
            {
                Console.WriteLine("Thread 1 acquired a lock on resource 2.");
            }
        }
    }
    static void Thread2Work()
    {
        lock (sharedObj1)
        {
            Console.WriteLine("Thread 2 has acquired a shared resource 2. " +
                "It is now waiting for acquiring a lock on resource 1");
            Thread.Sleep(1000);
            lock (sharedObj2)
            {
                Console.WriteLine("Thread 2 acquired a lock on resource 1.");
            }
        }
    }
}

Consulte as listagens de códigos originais e revisadas. Na listagem original, Threads Thread1work e Thread2work adquirem imediatamente bloqueios no SharedObJ1 e SharedObJ2, respectivamente. Em seguida, o Thread1work é suspenso até que o Thread2Work Reairs SharedObJ2. Da mesma forma, o Thread2work é suspenso até que o Thread1Work Slaveeds SharedObJ1. Como os dois threads adquirem bloqueios nos dois objetos compartilhados em ordem oposta, o resultado é uma dependência circular e, portanto, um impasse.

Na listagem revisada, os dois threads adquirem bloqueios nos dois objetos compartilhados na mesma ordem, garantindo assim que não haja possibilidade de uma dependência circular. Portanto, a listagem de código revisada mostra como você pode resolver qualquer situação de impasse em seu aplicativo, garantindo que todos os threads adquiram bloqueios em uma ordem consistente.

Práticas recomendadas para sincronização de threads

Embora muitas vezes seja necessário sincronizar o acesso a recursos compartilhados em um aplicativo, você deve usar a sincronização do encadeamento com os cuidados. Seguindo as práticas recomendadas da Microsoft, você pode evitar impasses ao trabalhar com a sincronização do thread. Aqui estão algumas coisas a serem lembradas:

  • Ao usar a palavra -chave de bloqueio ou o objeto System.Threading.Lock em C# 13, use um objeto de um tipo de referência privado ou protegido para identificar o recurso compartilhado. O objeto usado para identificar um recurso compartilhado pode ser qualquer instância de classe arbitrária.
  • Evite usar tipos imutáveis ​​em suas declarações de bloqueio. Por exemplo, o bloqueio em objetos de string pode causar impasse devido à internação (porque as seqüências de caracteres internadas são essencialmente globais).
  • Evite usar um bloqueio em um objeto que seja acessível ao público.
  • Evite usar declarações como Lock (this) para implementar a sincronização. Se o objeto estiver acessível ao público, os impasses podem resultar.

Observe que você pode usar tipos imutáveis ​​para aplicar a segurança do thread sem precisar gravar código que usa a palavra -chave de bloqueio. Outra maneira de alcançar a segurança do encadeamento é usar variáveis ​​locais para limitar seus dados mutáveis ​​a um único thread. Variáveis ​​e objetos locais estão sempre confinados a um thread. Em outras palavras, como os dados compartilhados são a causa raiz das condições de raça, você pode eliminar as condições de raça confinando seus dados mutáveis. No entanto, o confinamento derrota o objetivo da multi-threading, portanto, será útil apenas em determinadas circunstâncias.