Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O Visual Basic .NET ou Visual Basic oferece a capacidade de usar threads em aplicativos Visual Basic pela primeira vez. Os threads introduzem problemas de depuração, como condições de corrida e deadlocks. Este artigo explora essas duas questões.
Versão original do produto: Visual Basic, Visual Basic .NET
Número original do KB: 317723
Quando ocorrem condições de corrida
Uma condição de corrida ocorre quando dois threads acessam 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. Em seguida, o primeiro thread e o segundo thread executam suas operações no valor e correm para ver qual thread pode gravar o valor por último na variável compartilhada. O valor do thread que grava seu valor por último é preservado, pois o thread está gravando sobre o valor que o thread anterior escreveu.
Detalhes e exemplos para uma condição de corrida
Cada thread recebe um período de tempo predefinido para ser executado em um processador. Quando o tempo alocado para o thread expira, o contexto do thread é salvo até o próximo turno no processador e o processador inicia a execução do próximo thread.
Como um comando de uma linha pode causar uma condição de corrida
Examine o exemplo a seguir para ver como ocorre uma condição de corrida. Há dois threads e ambos estão atualizando uma variável compartilhada chamada total (que é representada como dword ptr ds:[031B49DCh]
no código do assembly).
Tópico 1
Total = Total + val1
Tópico 2
Total = Total - val2
Código assembly (com números de linha) da compilação do código Visual Basic anterior:
Tópico 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
Tópico 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 do assembly, você pode ver quantas operações o processador está executando no nível inferior para executar um cálculo de adição simples. Um thread pode ser capaz de executar todo ou parte de seu código assembly durante seu tempo no processador. Agora veja como uma condição de corrida ocorre a partir desse código.
Total
é 100, val1
é 50 e val2
é 15. O thread 1 tem a oportunidade de executar, mas conclui apenas as etapas 1 a 3. Isso significa que o Thread 1 leu a variável e concluiu a adição. O thread 1 agora está apenas esperando para gravar seu novo valor de 150. Depois que o Thread 1 é interrompido, o Thread 2 é executado completamente. Isso significa que ele escreveu o valor que calculou (85) para a variável Total
. Por fim, o Thread 1 recupera o controle e conclui a execução. Ele escreve seu valor (150). Portanto, quando o Thread 1 for concluído, o valor de Total
agora é 150 em vez de 85.
Você pode ver como isso pode ser um grande problema. Se este for um programa bancário, o cliente teria dinheiro em sua conta que não deveria estar presente.
Esse erro é aleatório, pois é possível que o Thread 1 conclua sua execução antes que o tempo no processador expire e, em seguida , o Thread 2 pode iniciar sua execução. Se esses eventos ocorrerem, 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 ser executados de forma diferente no modo de tempo de execução versus depuração. Além disso, você pode ver que, se executar cada thread em série, o erro não ocorrerá. Essa aleatoriedade torna esses erros muito mais difíceis de rastrear e depurar.
Para evitar que as condições de corrida ocorram, 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 estiver bloqueada no Thread 1 e o Thread 2 também precisar da variável, a execução do Thread 2 será interrompida enquanto o Thread 2 aguarda que o Thread 1 libere a variável. (Para obter mais informações, consulte SyncLock
a seção Referências deste artigo.)
Sintomas de uma condição de corrida
O sintoma mais comum de uma condição de corrida são valores imprevisíveis de variáveis que são compartilhadas entre vários threads. Isso resulta da imprevisibilidade da ordem em que os threads são executados. Às vezes, um thread vence e às vezes o outro thread. Em outros momentos, a execução funciona corretamente. Além disso, se cada thread for executado separadamente, o valor da variável se comportará corretamente.
Quando ocorrem deadlocks
Um deadlock ocorre quando dois threads bloqueiam uma variável diferente ao mesmo tempo e, em seguida, tentam bloquear a variável que o outro thread já bloqueou. Como resultado, cada thread para de ser executado e aguarda que o outro thread libere a variável. Como cada thread está mantendo a variável que o outro thread deseja, nada ocorre e os threads permanecem em deadlock.
Detalhes e exemplos de deadlocks
O código a seguir tem dois objetos LeftVal
e RightVal
:
Tópico 1
SyncLock LeftVal SyncLock RightVal 'Perform operations on LeftVal and RightVal that require read and write. End SyncLock End SyncLock
Tópico 2
SyncLock 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 tem permissão para bloquear LeftVal
. O processador interrompe a execução do Thread 1 e inicia a execução do Thread 2. O thread 2 trava RightVal
e tenta bloquear LeftVal
. Como LeftVal
está bloqueado, o Thread 2 para e aguarda LeftVal
a liberação. Como o Thread 2 está parado, o Thread 1 tem permissão para continuar em execução. O Thread 1 tenta bloquear RightVal
, mas não consegue, porque o Thread 2 o bloqueou. Como resultado, o Thread 1 começa a aguardar até que RightVal fique disponível. Cada thread aguarda o outro thread, porque cada thread bloqueou a variável que o outro thread está aguardando e nenhum thread está desbloqueando a variável que está mantendo.
Um deadlock nem sempre ocorre. Se o Thread 1 executar ambos os bloqueios antes que o processador o interrompa, o Thread 1 poderá executar suas operações e, em seguida, desbloquear a variável compartilhada. Depois que o Thread 1 desbloquear a variável, o Thread 2 poderá prosseguir com sua execução, conforme o esperado.
Esse erro parece óbvio quando esses trechos de código são colocados lado a lado, mas, na prática, o código pode aparecer em módulos ou áreas separadas do seu código. Esse é um erro difícil de rastrear porque, a partir desse mesmo código, pode ocorrer a execução correta e a execução incorreta.
Sintomas de deadlocks
Um sintoma comum de deadlock é que o programa ou grupo de threads para de responder. Isso também é conhecido como travamento. Pelo menos dois threads estão aguardando uma variável que o outro thread bloqueou. Os threads não prosseguem, porque nenhum dos threads liberará sua variável até obter a outra variável. Todo o programa pode travar se o programa estiver aguardando um ou ambos os threads para concluir a execução.
O que é um tópico
Os processos são usados para separar os diferentes aplicativos que estão sendo executados em um momento especificado em um único computador. O sistema operacional não executa processos, mas os threads sim. Um thread é uma unidade de execução. O sistema operacional aloca o tempo do processador a um thread para a execução das tarefas do thread. Um único processo pode conter vários threads de execução. Cada thread mantém seus próprios manipuladores de exceção, prioridades de agendamento e um conjunto de estruturas que o sistema operacional usa para salvar o contexto do thread se o thread não puder concluir sua execução durante o tempo em que foi atribuído ao processador. O contexto é mantido até a próxima vez que o thread receber o tempo do processador. O contexto inclui todas as informações que o thread requer para continuar sua execução sem problemas. Essas informações incluem o conjunto de registros de processador do thread e a pilha de chamadas dentro do espaço de endereço do processo de host.
Referências
Para obter mais informações, pesquise a Ajuda do Visual Studio para as seguintes palavras-chave:
SyncLock
. Permite que um objeto seja bloqueado. Se outro thread tentar bloquear o mesmo objeto, ele será bloqueado até que o primeiro thread seja liberado. UseSyncLock
com cuidado, pois os problemas podem resultar do uso indevido do SyncLock. Por exemplo, esse comando pode impedir condições de corrida, mas causar deadlocks.InterLocked
. Permite um conjunto selecionado de operações thread-safe em variáveis numéricas básicas.
Para obter mais informações, consulte Threads e threading.