VB .NET - Threads - DeadLock - O Conceito, o problema e a resolução


Neste artigo vou apresentar o conceito de DeadLock, mostrar um exemplo e também mostrar uma maneira de resolver o problema do DeadLock.
Tudo isso usando a linguagem VB .NET.

O que é DeadLock ?

A tradução de deadlock pode ser : impasse, beco sem saída

No contexto das Threads o DeadLock ocorre na seguinte situação: 

Uma Thread, ou segmento ou linha de execução é uma forma de um processo dividir a si mesmo em duas ou mais tarefas que podem ser executadas concorrentemente.

A linguagem C# suporta a execução paralela de código através do multithreading onde uma Thread é uma caminho de execução independente que esta apto para rodar simultaneamente com outras threads.

Todas as classes relacionadas à segmentação(threading) estão presentes no namespace System.Threading

Quando dois ou mais processos concorrentes (ou duas Threads) estão esperando pelo outro terminar e nenhum deles termina estamos em um impasse ou deadlock.


 

Cenário para ocorrência de um DeadLock

Vamos supor que temos 2 threads: 

a) Thread1
b) Thread2

E que temos dois recursos:

a) Recurso1
b) Recurso2


A Thread1 já adquiriu um bloqueio no Recurso1 e deseja adquirir um bloqueio no Recurso2.

Ao mesmo tempo a Thread2 já adquiriu um bloqueio no Recurso2 e deseja adquirir um bloqueio no Recurso1.

Uma thread fica esperando pela outra liberar o bloqueio e isso nunca acontece.

Temos assim um impasse , um beco sem saída, temos um DeadLock.

Vou mostrar agora um exemplo prático mostrando uma simulação da ocorrência do deadlock e como resolver o problema.

Recursos usados:

Nota: Baixe e use a versão Community 2015 do VS ela é grátis e é equivalente a versão Professional.

Criando a solução no VS Community

Abra o VS Community 2015 e clique em New Project;

Selecione a linguagem Visual Basic e o template Console Application;

Informe o nome VBNET_DeadLock e clique no botão OK;

Definindo o código e simulando o DeadLock

Vamos definir no início do módulo duas variáveis que serão usadas para adquirir o bloqueio:

Private ObjLockA As Object = New Object()
Private
ObjLockB As Object = New Object()

A seguir no método Main() inclua o código abaixo:

Sub Main()

        Console.WriteLine("----Exemplo de DeadLock-----")
        Console.WriteLine()
        ' Inicializa a thread com endereço Tarefa1
        Dim thread1 As New Thread(AddressOf Tarefa1)
        ' Inicializa a thread com endereço Tarefa2
        Dim thread2 As New Thread(AddressOf Tarefa2)
        ' Agenda a execução das threads
        thread1.Start()
        thread2.Start()
        thread1.Join()
        thread2.Join()
        ' Esta instrução nunca será executada
        Console.WriteLine("Processamento concluído...")
        Console.ReadKey()
    End Sub

Neste código estamos criando duas Threads, thread1 e thread2 e executando o método Tarefa1 e Tarefa2 respectivamente em cada thread.

Vamos definir o código do método Tarefa1:

 Private Sub Tarefa1()
        SyncLock ObjLockA
            Console.WriteLine("Tentando adquirir um bloqueio em ObjLockB")
            ' Pausa de 1 segundo
            Thread.Sleep(1000)
            SyncLock ObjLockB
                ' Este bloco nunca será executado
                Console.WriteLine("Tarefa1 - Seção Crítica.")
                ' Acesso ao recurso compartilhado
            End SyncLock
        End SyncLock
End Sub

Neste código usamos o método SyncLock para adquirir um bloqueio exclusivo no objeto ObjLockA para um recurso antes de executar o recurso.

A seguir damos uma pausa de 1s na execução da thread usando o método Thread.Sleep(1000) e a seguir solicitamos a aquisição de um bloqueio no objeto ObjLockB

Vamos definir o código do método Tarefa2:

 Private Sub Tarefa2()
        SyncLock ObjLockB
            Console.WriteLine("Tentando adquirir um bloqueio em ObjLockA")
            SyncLock ObjLockA
                ' Este bloco de código nunca será executado
                Console.WriteLine("Tarefa2 - Seção Crítica")
                 ' Acesso ao recurso compartilhado
            End SyncLock
        End SyncLock
End Sub

Neste código fazemos o mesmo que no código anterior invertendo os objetos de bloqueio.

Neste cenário ao executarmos o projeto iremos obter o seguinte resultado:

Temos aqui o DeadLock.

Pois a thread1 esta esperando que a thread2 libere o bloqueio para o objeto ObjLockB e a thread2 esperando que a thread1 libere o bloqueio para o objeto ObjLockA.

O programa ficará 'congelado' indefinidamente esperando pela liberação dos bloqueios que nunca ocorrerá.

Resolvendo o problema do DeadLock

Para resolver o problema do DeadLock podemos :

1- Adquirir um bloqueio em um ordem específica;
2- Usar a classe Mutex (mútuo exclusivo);
3- Usar o método Monitor.TryEnter();

Neste exemplo vamos usar a classe Monitor.

A classe Monitor provê um mecanismo que sincroniza o acesso aos objetos em um programa multitarefa. Ela controla o acesso aos objetos através da concessão de um bloqueio a um objeto para uma única thread.

O bloqueio do objeto fornece a capacidade de restringir o acesso a um bloco de código conhecido como sessão crítica. Enquanto uma thread possuir o bloqueio para o objeto nenhuma outra thread poderá adquirir o bloqueio.

Vamos usar o método Monitor.TryEnter que possui a seguinte sintaxe:

Monitor.TryEnter(object obj, int milisegundos)

Tenta obter um bloqueio exclusivo para um objeto especificado por número determinado de milisegundos.

Retorno:
true – se a thread adquiriu o bloqueio
false – se a thread não conseguir adquirir o bloqueio

Usamos os métodos Enter e Exit para marcar o início e o fim de uma sessão crítica de código.

Se a sessão crítica for um conjunto de instrução contíguas então o bloqueio adquirido pelo método Enter garante que somente uma única thread pode executar o código.

Neste caso é recomendado colocar as instruções em um bloco try/catch e usar o método Exit no bloco finally.

Vamos então alterar o código do método Tarefa2 conforme abaixo:

    Private Sub Tarefa1()
        SyncLock ObjLockA
            Console.WriteLine("Tentando adquirir um bloqueio em ObjLockB")
            ' Pausa de 1 segundo
            Thread.Sleep(1000)
            ' Tenta adquirir um bloqueio por 5 segundos
            If Monitor.TryEnter(ObjLockB, 5000) Then
                Try
                    ' Este bloco nunca será executado
                    Console.WriteLine("Tarefa1 - Seção Crítica.")
                    ' Acesso ao recurso compartilhado
                Finally
                    Monitor.[Exit](ObjLockB)
                End Try
            Else
                Console.WriteLine("Não foi possível adquirir um bloqueio, saindo de Tarefa1...")
            End If
        End SyncLock
    End Sub

Agora usando o método Monitor.TryEnter(objLockB,5000) tentamos adquirir um bloqueio para o objeto ObjLockB por 5 segundos e se não for possível liberamos a tentativa de bloqueia usando o método Monitor.Exit(ObjLockB)

Executando o projeto novamente agora iremos obter o seguinte resultado:

E assim escapamos do DeadLock.

Pegue o projeto completo aqui: VBNET_DeadLock.zip

Até o próximo artigo...

(Disse Jesus aos fariseus) Hipócritas, bem profetizou Isaías a vosso respeito, dizendo:
Este povo se aproxima de mim com a sua boca e me honra com os seus lábios, mas o seu coração está longe de mim.
Mas, em vão me adoram, ensinando doutrinas que são preceitos dos homens.

Mateus 15:7-9

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Quer aprender a criar aplicações Web Dinâmicas usando a ASP .NET MVC 5 ?

 

  Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter

 

Referências:


José Carlos Macoratti