Resumo : neste tutorial, você aprenderá sobre o impasse do C#, como ele ocorre e como corrigi-lo.
Introdução ao impasse C#
Um deadlock acontece quando dois ou mais threads estão bloqueando e esperando um pelo outro para liberar os bloqueios necessários para prosseguir.
Particularmente, cada thread mantém um bloqueio que o outro thread precisa e nenhum deles pode prosseguir até adquirir o bloqueio que está esperando.
Por exemplo, suponha que você tenha dois threads t1 e t2. Cada thread mantém um bloqueio em um recurso compartilhado diferente r1 e r2.
O thread t1 precisa acessar r2 para prosseguir enquanto o thread t2 precisa acessar r1. Ambos os threads tentam adquirir os recursos ao mesmo tempo. Eles estão esperando e bloqueando um ao outro. Como resultado, ocorre um impasse.
O programa a seguir demonstra como ocorre um cenário de deadlock quando dois threads completam vários bloqueios:
using static System.Console;
var lock1 = new object();
var lock2 = new object();
void AcquireLocks1()
{
var threadId = Thread.CurrentThread.ManagedThreadId;
lock (lock1)
{
WriteLine($"Thread {threadId} acquired lock 1.");
Thread.Sleep(1000);
WriteLine($"Thread {threadId} attempted to acquire lock 2.");
lock (lock2)
{
WriteLine($"Thread {threadId} acquired lock 2.");
}
}
}
void AcquireLocks2()
{
var threadId = Thread.CurrentThread.ManagedThreadId;
lock (lock2)
{
WriteLine($"Thread {threadId} acquired lock 2.");
Thread.Sleep(1000);
WriteLine($"Thread {threadId} attempted to acquire lock 1.");
lock (lock1)
{
WriteLine($"Thread {threadId} acquired lock 1.");
}
}
}
// Create two new threads that compete for the locks
var thread1 = new Thread(AcquireLocks1);
var thread2 = new Thread(AcquireLocks2);
// Start the threads
thread1.Start();
thread2.Start();
// Wait for the threads to complete
thread1.Join();
thread2.Join();
WriteLine("Finished.");
ReadLine();
Linguagem de código: C# ( cs )
Como funciona.
Primeiro, crie dois objetos de bloqueio lock1 e lock2:
var lock1 = new object();
var lock2 = new object();
Linguagem de código: C# ( cs )
Segundo, defina o AcquireLock1
método que adquire lock1, atrasa um segundo e tenta adquirir lock2:
void AcquireLocks1()
{
var threadId = Thread.CurrentThread.ManagedThreadId;
lock (lock1)
{
WriteLine($"Thread {threadId} acquired lock 1.");
Thread.Sleep(1000);
WriteLine($"Thread {threadId} attempted to acquire lock 2.");
lock (lock2)
{
WriteLine($"Thread {threadId} acquired lock 2.");
}
}
}
Linguagem de código: C# ( cs )
Terceiro, defina AcquireLock2()
o método que adquire lock2, atrasa um segundo e tenta adquirir lock1:
void AcquireLocks2()
{
var threadId = Thread.CurrentThread.ManagedThreadId;
lock (lock2)
{
WriteLine($"Thread {threadId} acquired lock 2.");
Thread.Sleep(1000);
WriteLine($"Thread {threadId} attempted to acquire lock 1.");
lock (lock1)
{
WriteLine($"Thread {threadId} acquired lock 1.");
}
}
}
Linguagem de código: C# ( cs )
Quarto, crie dois threads, inicie-os e espere que sejam concluídos:
// Create two new threads that compete for the locks
var thread1 = new Thread(AcquireLocks1);
var thread2 = new Thread(AcquireLocks2);
// Start the threads
thread1.Start();
thread2.Start();
// Wait for the threads to complete
thread1.Join();
thread2.Join();
Linguagem de código: C# ( cs )
Saída:
Thread 10 acquired lock 1.
Thread 11 acquired lock 2.
Thread 10 attempted to acquire lock 2.
Thread 11 attempted to acquire lock 1.
Linguagem de código: C# ( cs )
Thread1 executa o método AcquireLock1 enquanto thread2 executa o AcquireLock2
método.
Como o thread1 está mantendo o lock1, o thread2 está impedido de adquirir o lock1. Portanto, thread2 está aguardando que thread1 libere o lock1.
Enquanto isso, thread2 está mantendo o lock2 e thread1 está impedido de adquirir o lock1. O thread1 está aguardando o thread2 liberar o lock2.
Ambos os threads estão esperando um pelo outro para obter os bloqueios correspondentes antes de prosseguir, o que resulta em um impasse.
Como resultado, o programa irá travar indefinidamente e você nunca verá a mensagem “Concluído” no console.
Resolvendo um impasse
Para corrigir um impasse, você pode usar uma das seguintes técnicas:
- Evite manter um bloqueio por muito tempo: quando um thread mantém um bloqueio por muito tempo, pode causar um conflito. Para evitá-lo, você precisa liberar o bloqueio assim que não precisar dele.
- Evite bloqueios aninhados: é difícil gerenciar bloqueios aninhados. E uma das causas comuns de impasse é o uso de bloqueios aninhados. Se você precisar usar vários bloqueios, tente adquiri-los em uma ordem fixa para evitar impasses.
- Use um tempo limite: o tempo limite pode ajudar a evitar a ocorrência de um conflito. Se um thread estiver aguardando um bloqueio por um determinado tempo que exceda o tempo limite, você poderá lançar uma exceção ou executar outras ações.
- Use a técnica de programação assíncrona: a programação assíncrona permite que vários threads sejam executados simultaneamente sem bloquear uns aos outros, o que ajuda a evitar conflitos.
O programa a seguir demonstra como usar um tempo limite para corrigir um impasse no programa acima:
using static System.Console;
var lock1 = new object();
var lock2 = new object();
void AcquireLocks1()
{
var threadId = Thread.CurrentThread.ManagedThreadId;
while (true)
{
if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(1)))
{
try
{
WriteLine($"Thread {threadId} acquired lock 1.");
Thread.Sleep(1000);
WriteLine($"Thread {threadId} attempted to acquire lock 2.");
if (Monitor.TryEnter(lock2, TimeSpan.FromSeconds(1)))
{
try
{
WriteLine($"Thread {threadId} acquired lock 2.");
break; // exit the loop if both locks are acquired
}
finally
{
Monitor.Exit(lock2);
}
}
}
finally
{
Monitor.Exit(lock1);
}
}
}
WriteLine($"Thread {threadId} is done.");
}
void AcquireLocks2()
{
var threadId = Thread.CurrentThread.ManagedThreadId;
while (true)
{
if (Monitor.TryEnter(lock2, TimeSpan.FromSeconds(1)))
{
try
{
WriteLine($"Thread {threadId} acquired lock 2.");
Thread.Sleep(1000);
WriteLine($"Thread {threadId} attempted to acquire lock 1.");
if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(1)))
{
try
{
WriteLine($"Thread {threadId} acquired lock 1.");
break; // exit the loop if both locks are acquired
}
finally
{
Monitor.Exit(lock1);
}
}
}
finally
{
Monitor.Exit(lock2);
}
}
}
WriteLine($"Thread {threadId} is done.");
}
// Create two new threads that compete for the locks
var thread1 = new Thread(AcquireLocks1);
var thread2 = new Thread(AcquireLocks2);
// Start the threads
thread1.Start();
thread2.Start();
// Wait for the threads to complete
thread1.Join();
thread2.Join();
WriteLine("Finished.");
ReadLine();
Linguagem de código: C# ( cs )
Neste programa, utilizamos o
para tentar adquirir um bloqueio com tempo limite de um segundo.Monitor.TryEnter
()
Se um thread não conseguir adquirir o bloqueio em um segundo, ele libera o bloqueio e tenta novamente.
O loop while continua tentando até que ambos os threads possam adquirir os bloqueios, o que interrompe o loop e os threads completam suas execuções.
Resumo
- Um deadlock ocorre quando dois ou mais threads estão aguardando e bloqueando um ao outro para liberar bloqueios.