Ir para o conteúdo

Semáforos e acesso concorrente em VB.Net

Nos recursos computacionais, é muito frequente o acesso em simultâneo por várias aplicações: um exemplo muito comum é, por exemplo, a escrita de ficheiros. Ora, como se sabe, a escrita ao mesmo tempo por duas aplicações num ficheiro é devidamente controlada, caso contrário o ficheiro iria ficar com conteúdos inimagináveis (leia-se corrompido).

Outro exemplo comum, fora de âmbito da informática, é a presença de semáforos numa determinada rua: vamos supor que a rua tem dois sentidos, cuja passagem é controlada apenas por um único semáforo, e uma passadeira, que também possui um semáforo próprio para peões. Assumindo que o sinal está livre para os automóveis, e um peão pretende atravessar a passadeira, a seguinte sequência irá desencadear-se:

Acção Semáforo Automóveis Semáforo Peões
Estado inicial Aberto Fechado
Peão pede para atravessar A fechar... Fechado
Peão atravessa Fechado Aberto
Fim do tempo para atravessar Fechado A fechar...
Automobilistas arrancam Aberto Fechado

E assim sucessivamente. Agora que a noção de semáforo está relembrada, passemos ao respectivo código VB.Net.

Acesso concorrente a um array

Neste exemplo, vamos ver como duas threads acedem ao mesmo array concorrentemente. No caso concreto, pretende-se simular um fluxo de entrada de dados, que preenche o array com valores vindos de uma fonte cuja recepção é demorada, e simular um fluxo de saída, imediato (sem esperas), da escrita dos valores obtidos para o ecrã.

Como regra, os valores só devem ser impressos quando o array estiver completamente preenchido. Após isso, o array é "limpo" (preenchido com zeros) e volta a buscar valores. Pode-se perfeitamente fazer uma analogia com o exemplo dos automóveis e peões, pois partem do mesmo princípio dos semáforos: a aplicação começa por ler os valores, e após o array estar preenchido, termina a leitura, prosseguindo com a escrita para o ecrã; após isso, o fluxo de escrita para ecrã fecha, e novos valores são pedidos, e assim sucessivamente.

Imports System.Threading

Module Semaforos
    Private lista(4) As Integer
    ' irá ser o semáforo para controlar a escrita no array
    Private semEscrita As Semaphore
    ' irá ser o semáforo para controlar a leitura de dados para o ecrã
    Private semLeitura As Semaphore

    Sub Main()
        ' Inicizaliação de semáforos

        ' Iniciar o semáforo de escrita a 1
        ' significa que tem "direito" a uma escrita
        semEscrita = New Semaphore(1, 1)

        ' Iniciar o semáforo de leitura a 0
        ' significa que, de momento, se encontra bloqueado
        semLeitura = New Semaphore(0, 1)

        ' Vamos criar novas threads para que escrevam e leiam
        ' ao mesmo tempo do array 'lista',mas de forma controlada
        ' com recurso a semáforos semáforos
        Dim t As New Thread(AddressOf obterDados)
        t.Start()

        lerArray()
    End Sub

    ''' <summary>
    ''' Função que obtém dados de forma fictícia, com um pequeno atraso
    ''' </summary>
    Private Sub obterDados()
        While (1)
            ' Caso o valor do semáforo seja 0, a thread irá
            ' bloquear aqui, até que o semáforo obtenha "luz verde"
            ' Na outra função está a ocorrer uma leitura de dados
            semEscrita.WaitOne()

            ' simulação de entrada de dados
            For i = 0 To lista.Length - 1
                lista(i) = New Random().Next(1, 11)
                Debug.WriteLine("escreve " & lista(i))
                ' pequeno atraso para simular uma fonte de dados demorada
                Thread.Sleep(500)
            Next

            ' Dar "luz verde" ao semáforo de leitura, visto que já foram
            ' introduzidos valores, a função de leitura pode lê-los sem problemas
            semLeitura.Release()
        End While
    End Sub

    ''' <summary>
    ''' Função que lê o array e imprime os valores no ecrã
    ''' </summary>
    Private Sub lerArray()
        While (1)
            ' Caso o valor do semáforo seja 0, a thread irá
            ' bloquear aqui, até que o semáforo obtenha "luz verde"
            ' Na outra função, está a ocorrer a escrita de dados no array
            semLeitura.WaitOne()

            ' leitura de dados e escrita no ecrã
            For i = 0 To lista.Length - 1
                Console.Write(lista(i) & " ")
                Debug.WriteLine("lê " & lista(i))
                lista(i) = 0
            Next
            Console.WriteLine()

            ' Dar "luz verde" ao semáforo de escrita, uma vez que os dados foram 
            ' lidos e podem ser substituídos por outros vindos da fonte externa
            semEscrita.Release()
        End While
    End Sub
End Module

Este pequeno programa inclui também mensagens de debug para cada introdução/leitura de valores, pelo que poderá ser útil dar uma olhadela à "Immediate Window" para acompanhar o progresso da aplicação.

Importante: Ao longo dos comentários foram usadas expressões comuns para que a explicação ficasse acessível a utilizadores menos experientes. Para ser mais correcto, dever-se-iam usar as expressões incrementar e decrementar semáforo para as operações de "dar luz verde" e bloquear a thread, respectivamente.