Descrição das condições de disputa e deadlocks


Sumário


Visual Basic .NET ou Visual Basic 2005 oferece a capacidade de usar threads em aplicativos Visual Basic pela primeira vez. Threads introduzem problemas de depuração, como condições de disputa e deadlocks. Este artigo explora esses dois problemas.

Mais informações


Condições de corrida

Uma condição de corrida ocorre quando dois threads acessem uma variável compartilhada ao mesmo tempo. O primeiro thread lê a variável e o segundo thread lê o mesmo valor da variável. O encadeamento de primeiro e segundo thread executam suas operações no valor e correm para ver qual thread pode gravar o valor da última variável compartilhada. O valor do segmento que grava o valor última será preservado, porque o thread está escrevendo sobre o valor que escreveu o segmento anterior.

Detalhes e exemplo

Cada thread é alocado um período predefinido de tempo de execução no processador. Quando o tempo de alocação para thread expira, o contexto da thread é salvo até a próxima vez no processador e o processador começa a execução da próxima thread.

Como um uma linha de comando pode causar uma condição de corrida? Examine o exemplo a seguir para ver como uma condição de corrida ocorre. Há dois segmentos, e ambos estão atualizando uma variável compartilhada chamada total (que é representado como dword ptr ds: [031B49DCh] no código de assembly).

Código do Visual Basic:
   'Thread 1   Total = Total + val1

   'Thread 2   Total = Total - val2

Código de assembly (com números de linha) de compilação do código do Visual Basic anterior:
 'Thread 1 1.   mov         eax,dword ptr ds:[031B49DCh] 
2. add eax,edi
3. jno 00000033
4. xor ecx,ecx
5. call 7611097F
6. mov dword ptr ds:[031B49DCh],eax

 'Thread 2 1.   mov         eax,dword ptr ds:[031B49DCh] 
2. sub eax,edi
3. jno 00000033
4. xor ecx,ecx
5. call 76110BE7
6. mov dword ptr ds:[031B49DCh],eax

Examinando o código de assembly, você pode ver quantas operações o processador está executando no nível inferior para executar um cálculo simples de adição. Um segmento pode ser capaz de executar todo ou parte do seu código de assembly durante o tempo do processador. Agora veja como uma condição de corrida ocorre deste código.

O total é 100, val1 é 50 e val2 é 15. O thread 1 obtém uma oportunidade de executar, mas apenas conclui as etapas 1 a 3. Isso significa que o Thread 1 lê a variável e conclui a adição. Segmento 1 agora é só esperando para gravar o novo valor de 150. Depois de Thread 1 for interrompido, o Thread 2 obtém seja completamente executada. Isso significa que ele escreveu o valor que ela calculada (85)-out ao Total da variável. Finalmente, o Thread 1 recupera o controle e finaliza a execução. Ele grava seu valor (150). Portanto, quando o Thread 1 for concluído, o valor do Total agora é 150 em vez de 85.


Você pode ver como isso pode ser um grande problema. Se fosse um programa bancário, o cliente tem dinheiro em sua conta não deve estar presente.


Este erro é aleatório, porque é possível que o Thread 1 ao concluir sua execução antes de seu tempo expira o processador e, em seguida, o Thread 2 pode começar sua execução. Se esses eventos ocorrem, o problema não ocorrerá. A execução do thread é não-determinística, portanto você não pode controlar o tempo ou a ordem de execução. Observe também que os threads podem executar diferente em tempo de execução em vez do modo de depuração. Além disso, você pode ver que se você executar cada thread em série, o erro não ocorre. Esta aleatoriedade torna esses erros muito mais difíceis de rastrear e depurar.


Para impedir a ocorrência de condições de corrida, você pode bloquear variáveis compartilhadas, para que apenas um thread por vez tenha acesso à variável compartilhada. Faça isso com moderação, pois se uma variável está bloqueada no Thread 1 e Thread 2 também precisa da variável, a execução do Thread 2 para enquanto o Thread 2 aguarda Thread 1 liberar a variável. (Para obter mais informações, consulte "SyncLock" na seção "Referências" deste artigo.)

Sintomas

O sintoma mais comum de uma condição de corrida é imprevisíveis valores de variáveis que são compartilhados entre vários threads. Isso é resultado da imprevisibilidade da ordem na qual os threads de execução. Um dia wins de um thread e um dia o outro wins thread. Em outros momentos, a execução funciona corretamente. Além disso, se cada thread é executada separadamente, o valor da variável funciona corretamente.


Travamentos

Um deadlock ocorre quando dois threads cada bloquear uma variável diferente ao mesmo tempo e tentam bloquear a variável que outro thread já está bloqueado. Como resultado, cada thread interromperá a execução e aguarda o segmento liberar a variável. Como cada thread está mantendo a variável que deseja que o outro thread, não acontece nada, e os segmentos permanecem bloqueados.

Detalhes e exemplo

O código a seguir possui dois objetos, LeftVal e RightVal:

'Thread 1SyncLock LeftVal
SyncLock RightVal
'Perform operations on LeftVal and RightVal that require read and write.
End SyncLock
End SyncLock

'Thread 2SyncLock RightVal
SyncLock LeftVal
'Perform operations on RightVal and LeftVal that require read and write.
End SyncLock
End SyncLock

Um deadlock ocorre quando o Thread 1 é permitido para bloquear LeftVal. O processador interrompe a execução do Thread 1 e começa a execução do Thread 2. Thread 2 bloqueios RightVal e, em seguida, tenta bloquear LeftVal. Porque LeftVal está bloqueado, o Thread 2 para e aguarda LeftVal a ser liberado. Porque o Thread 2 for interrompido, Thread 1 é permitida para continuar a execução. O thread 1 tenta bloquear RightVal mas não, porque o Thread 2 bloqueou. Como resultado, o Thread 1 começa a aguardar RightVal torna-se disponível. Cada segmento espera por outro thread, porque cada thread bloqueou a variável que outro thread está aguardando e nenhum thread é desbloquear a variável está mantendo.


Um deadlock não ocorra. Se o Thread 1 executa os dois bloqueios antes que o processador interrompe, Thread 1 pode executar suas operações e desbloquear a variável compartilhada. Após 1 Thread desbloqueia a variável, Thread 2 pode prosseguir com sua execução, conforme o esperado.


Este erro parece óbvio quando esses trechos de código são colocados lado a lado, mas na prática, o código poderá aparecer em módulos separados ou áreas do seu código. Este um erro muito difícil rastrear porque, deste mesmo código, execução correta e execução incorreta podem ocorrer.

Sintomas

Um sintoma comum de bloqueio é que o programa ou grupo de threads para de responder. Isso também é conhecido como um travamento. Pelo menos dois segmentos estão esperando uma variável que outro thread bloqueado. Os segmentos não continuar, pois nenhum thread irá lançar sua variável até que ele chegue a outra variável. O programa pode travar se o programa está aguardando um ou ambos os threads para execução completa.

O que é um Thread?

Processos são usados para separar os diferentes aplicativos que estão em execução em um horário especificado em um único computador. O sistema operacional não executa processos, mas sim de threads. Um segmento é uma unidade de execução. O sistema operacional aloca tempo do processador para um segmento para a execução de tarefas do segmento. Um único processo pode conter vários threads de execução. Cada segmento mantém seus próprios manipuladores de exceção, agendamento de prioridades e um conjunto de estruturas que o sistema operacional usa para salvar o contexto do thread se o thread não pode concluir sua execução durante o tempo em que ele foi atribuído ao processador. O contexto é mantido até a próxima vez que o thread recebe o tempo do processador. O contexto inclui todas as informações que o segmento requer perfeitamente continuar sua execução. Essas informações incluem o conjunto do segmento de registradores do processador e a pilha de chamadas dentro do espaço de endereço do processo do host.

Referências


Para obter mais informações, pesquise na Ajuda do Visual Studio para as seguintes palavras-chave:
  • SyncLock. Permite que um objeto seja bloqueado. Se outro thread tenta bloquear o mesmo objeto, ele será bloqueado até que o primeiro thread libera. Use SyncLock cuidadosamente, pois pode haver problemas contra o uso indevido do SyncLock. Por exemplo, este comando pode evitar condições de corrida, mas causar deadlocks.
  • Grudadas. Permite que um conjunto selecionado de thread-safe operações básicas variáveis numéricas.
Para obter informações adicionais, clique no número abaixo para ler o artigo na Base de Conhecimento Microsoft:

316422 informações: roteiro para Threading no Visual Basic .NET
Para obter mais informações, consulte o seguinte site da MSDN: