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.