8. Os threads de execução
8.1. Introdução
Quando se inicia uma aplicação, esta é executada num fluxo de execução denominado «thread». A classe .NET, que modela um thread, é a classe System.Threading.Thread e tem a seguinte definição:

Iremos utilizar apenas algumas das propriedades e métodos desta classe:
retorna o thread atualmente em execução | |
nome do thread | |
indica se o thread está ativo (true) ou não (false) | |
inicia a execução de um thread | |
interrompe definitivamente a execução de um thread | |
suspende a execução de um thread durante n milissegundos | |
suspende temporariamente a execução de um thread | |
retoma a execução de um thread suspenso | |
operação bloqueante — aguarda o fim do thread para passar à instrução seguinte |
Vejamos uma primeira aplicação que ilustra a existência de um thread principal de execução, aquele no qual é executada a função Main de uma classe:
' utilização de threads
Imports System
Imports System.Threading
Public Module thread1
Public Sub Main()
' inicialização do thread atual
Dim main As Thread = Thread.CurrentThread
' exibição
Console.Out.WriteLine(("Thread courant : " + main.Name))
' alteração do nome
main.Name = "main"
' verificação
Console.Out.WriteLine(("Thread courant : " + main.Name))
' loop infinito
While True
' exibição
Console.Out.WriteLine((main.Name + " : " + DateTime.Now.ToString("hh:mm:ss")))
' paragem temporária
Thread.Sleep(1000)
End While
End Sub
End Module
Resultados no ecrã:
dos>thread1
Thread courant :
Thread courant : main
main : 06:13:55
main : 06:13:56
main : 06:13:57
main : 06:13:58
main : 06:13:59
O exemplo anterior ilustra os seguintes pontos:
- a função Main é executada corretamente num thread
- tem-se acesso às características desse thread através de Thread.CurrentThread
- o papel do método Sleep. Aqui, o thread que executa Main entra em suspensão regularmente durante 1 segundo entre duas exibições.
8.2. Criação de threads de execução
É possível ter aplicações em que partes de código são executadas de forma «simultânea» em diferentes threads de execução. Quando se diz que os threads são executados simultaneamente, trata-se frequentemente de um uso incorreto da linguagem. Se a máquina tiver apenas um processador, como ainda é frequentemente o caso, os threads partilham esse processador: cada um dispõe dele, à vez, durante um breve instante (alguns milissegundos). É isso que dá a ilusão de paralelismo de execução. O tempo atribuído a um thread depende de vários fatores, incluindo a sua prioridade, que tem um valor por predefinição, mas que também pode ser definida por programação. Quando um thread dispõe do processador, utiliza-o normalmente durante todo o tempo que lhe foi atribuído. No entanto, pode libertá-lo antes do tempo:
- entrando em espera por um evento (wait, join, suspend)
- entrando em suspensão durante um período determinado (sleep)
- Um thread T é inicialmente criado pelo seu construtor
ThreadStart é do tipo delegate e define o protótipo de uma função sem parâmetros:
Uma construção clássica é a seguinte:
A função run, passada como parâmetro, será executada no arranque do thread.
- A execução do thread T é iniciada por T.Start(): a função [run], passada ao construtor de T, será então executada pelo thread T. O programa que executa a instrução T.start() não aguarda o fim da tarefa T: passa imediatamente para a instrução seguinte. Temos, assim, duas tarefas a serem executadas em paralelo. Muitas vezes, estas têm de poder comunicar entre si para saber em que ponto se encontra o trabalho comum a realizar. Este é o problema da sincronização das threads.
- Uma vez iniciado, o thread executa-se de forma autónoma. Parará quando a função start que está a executar tiver concluído o seu trabalho.
- É possível enviar determinados sinais à tarefa T:
- T.Suspend() indica-lhe que pare momentaneamente
- T.Resume() indica-lhe que retome o seu trabalho
- T.Abort() indica-lhe que pare definitivamente
- Também é possível aguardar o fim da sua execução através de T.join(). Trata-se de uma instrução bloqueante: o programa que a executa fica bloqueado até que a tarefa T tenha concluído o seu trabalho. É um meio de sincronização.
Analisemos o seguinte programa:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Imports System.Threading
Module thread2
Public Sub Main()
' inicialização do thread atual
Dim main As Thread = Thread.CurrentThread
' atribuir um nome à thread
main.Name = "main"
' criação de threads de execução
Dim tâches(4) As Thread
Dim i As Integer
For i = 0 To tâches.Length - 1
' criação do thread i
tâches(i) = New Thread(New ThreadStart(AddressOf affiche))
' define-se o nome do thread
tâches(i).Name = "tache_" & i
' inicia-se a execução do thread i
tâches(i).Start()
Next i
' fim da rotina
Console.Out.WriteLine(("fin du thread " + main.Name))
End Sub
Public Sub affiche()
' exibição do início da execução
Console.Out.WriteLine(("Début d'exécution de la méthode affiche dans le Thread " + Thread.CurrentThread.Name + " : " + DateTime.Now.ToString("hh:mm:ss")))
' suspensão durante 1 s
Thread.Sleep(1000)
' exibição do fim da execução
Console.Out.WriteLine(("Fin d'exécution de la méthode affiche dans le Thread " + Thread.CurrentThread.Name + " : " + DateTime.Now.ToString("hh:mm:ss")))
End Sub
End Module
O thread principal, aquele que executa a função Main, cria mais 5 threads encarregados de executar o método estático affiche. Os resultados são os seguintes:
dos>thread2
fin du thread main
Début d'exécution de la méthode affiche dans le Thread tache_0 : 05:27:53
Début d'exécution de la méthode affiche dans le Thread tache_1 : 05:27:53
Début d'exécution de la méthode affiche dans le Thread tache_2 : 05:27:53
Début d'exécution de la méthode affiche dans le Thread tache_3 : 05:27:53
Début d'exécution de la méthode affiche dans le Thread tache_4 : 05:27:53
Fin d'exécution de la méthode affiche dans le Thread tache_0 : 05:27:54
Fin d'exécution de la méthode affiche dans le Thread tache_1 : 05:27:54
Fin d'exécution de la méthode affiche dans le Thread tache_2 : 05:27:54
Fin d'exécution de la méthode affiche dans le Thread tache_3 : 05:27:54
Fin d'exécution de la méthode affiche dans le Thread tache_4 : 05:27:54
Estes resultados são muito esclarecedores:
- em primeiro lugar, verifica-se que o início da execução de um thread não é bloqueante. O método Main iniciou a execução de 5 threads em paralelo e terminou a sua execução antes deles. A operação
inicia a execução do thread «tâscas[i]», mas, feito isso, a execução prossegue imediatamente com a instrução seguinte, sem aguardar o fim da execução do thread.
- Todos os threads criados devem executar o método affiche. A ordem de execução é imprevisível. Embora, no exemplo, a ordem de execução pareça seguir a ordem dos pedidos de execução, não se podem tirar conclusões gerais a partir disso. O sistema operativo tem, neste caso, 6 threads e um processador. Irá distribuir o processador por estas 6 threads de acordo com regras que lhe são próprias.
- Nos resultados, observa-se uma consequência do método Sleep. No exemplo, é o thread 0 que executa em primeiro lugar o método affiche. A mensagem de início de execução é apresentada e, em seguida, o método Sleep é executado, o que o suspende durante 1 segundo. Perde então o processador, que fica assim disponível para outro thread. O exemplo mostra que é o thread 1 que o irá obter. A thread 1 seguirá o mesmo percurso, tal como as outras threads. Quando o segundo de espera da thread 0 terminar, a sua execução pode recomeçar. O sistema atribui-lhe o processador e ela pode concluir a execução do método affiche.
Alteremos o nosso programa para concluir o método Main com as instruções:
A execução do novo programa resulta em:
Os threads criados pela função Main não são executados. Trata-se da instrução
que causa isto: ela elimina todos os threads da aplicação e não apenas o thread Main. A solução para este problema consiste em fazer com que o método Main aguarde a conclusão da execução dos threads que criou antes de terminar a sua própria execução. Isto pode ser feito com o método Join da classe Thread:
' aguarda-se o fim da execução de todos os threads
For i = 0 To tâches.Length - 1
' aguardando o fim da execução do thread i
tâches(i).Join()
Next i 'for
' fim da rotina
Console.Out.WriteLine(("fin du thread " + main.Name))
Environment.Exit(0)
Obtêm-se então os seguintes resultados:
Début d'exécution de la méthode affiche dans le Thread tache_1 : 05:34:48
Début d'exécution de la méthode affiche dans le Thread tache_2 : 05:34:48
Début d'exécution de la méthode affiche dans le Thread tache_3 : 05:34:48
Début d'exécution de la méthode affiche dans le Thread tache_4 : 05:34:48
Début d'exécution de la méthode affiche dans le Thread tache_0 : 05:34:48
Fin d'exécution de la méthode affiche dans le Thread tache_2 : 05:34:50
Fin d'exécution de la méthode affiche dans le Thread tache_1 : 05:34:50
Fin d'exécution de la méthode affiche dans le Thread tache_3 : 05:34:50
Fin d'exécution de la méthode affiche dans le Thread tache_0 : 05:34:50
Fin d'exécution de la méthode affiche dans le Thread tache_4 : 05:34:50
fin du thread main
8.3. Importância dos threads
Agora que destacámos a existência de um thread por predefinição, aquele que executa o método Main, e que sabemos como criar outros, vamos debruçar-nos sobre a utilidade dos threads para nós e sobre a razão pela qual os apresentamos aqui. Existe um tipo de aplicações que se presta bem à utilização de threads: as aplicações cliente-servidor da Internet. Numa aplicação deste tipo, um servidor localizado numa máquina S1 responde aos pedidos de clientes localizados em máquinas remotas C1, C2, ..., Cn.
![]() |
Utilizamos diariamente aplicações da Internet que correspondem a este esquema: serviços Web, correio eletrónico, consulta de fóruns, transferência de ficheiros... No esquema acima, o servidor S1 deve servir os clientes Ci de forma simultânea. Se tomarmos o exemplo de um servidor FTP (File Transfer Protocol) que fornece ficheiros aos seus clientes, sabemos que uma transferência de ficheiros pode, por vezes, demorar várias horas. É claro que está fora de questão que um cliente monopolize sozinho o servidor durante tanto tempo. O que se faz habitualmente é o servidor criar tantos threads de execução quantos forem os clientes. Cada thread fica então encarregado de atender a um cliente específico. Como o processador é partilhado ciclicamente entre todos os threads ativos da máquina, o servidor dedica algum tempo a cada cliente, garantindo assim a simultaneidade do serviço.
![]() |
8.4. Acesso a recursos partilhados
No exemplo cliente-servidor acima referido, cada thread atende um cliente de forma amplamente independente. No entanto, as threads podem ter de cooperar para prestar o serviço solicitado ao seu cliente, nomeadamente no que diz respeito ao acesso a recursos partilhados. O esquema acima faz lembrar os balcões de uma grande administração, como uma estação de correios, por exemplo, onde, em cada balcão, um funcionário atende um cliente. Suponhamos que, de vez em quando, esses funcionários tenham de fazer fotocópias de documentos trazidos pelos seus clientes e que haja apenas uma fotocopiadora. Dois funcionários não podem utilizar a fotocopiadora ao mesmo tempo. Se o funcionário i encontrar a fotocopiadora a ser utilizada pelo funcionário j, terá de esperar. A esta situação chama-se «acesso a um recurso partilhado» e, em informática, é bastante delicada de gerir. Vejamos o seguinte exemplo:
- uma aplicação vai gerar n threads, sendo n passado como parâmetro
- o recurso partilhado é um contador que deverá ser incrementado por cada thread gerado
- No final da aplicação, o valor do contador é apresentado. Deveríamos, portanto, encontrar n.
O programa é o seguinte:
' opções
Option Explicit On
Option Strict On
' utilização de threads
Imports System
Imports System.Threading
Public Class thread3
' variáveis de classe
Private Shared cptrThreads As Integer = 0
Public Overloads Shared Sub Main(ByVal args() As [String])
' manual de instruções
Const syntaxe As String = "pg nbThreads"
Const nbMaxThreads As Integer = 100
' verificação do número de argumentos
If args.Length <> 1 Then
' erro
Console.Error.WriteLine(syntaxe)
' paragem
Environment.Exit(1)
End If
' verificação da qualidade do argumento
Dim nbThreads As Integer = 0
Try
nbThreads = Integer.Parse(args(0))
If nbThreads < 1 Or nbThreads > nbMaxThreads Then
Throw New Exception
End If
Catch
' erro
Console.Error.WriteLine("Nombre de threads incorrect (entre 1 et " & nbMaxThreads & ")")
' fim
Environment.Exit(2)
End Try
' criação e geração de threads
Dim threads(nbThreads - 1) As Thread
Dim i As Integer
For i = 0 To nbThreads - 1
' criação
threads(i) = New Thread(New ThreadStart(AddressOf incrémente))
' denominação
threads(i).Name = "tache_" & i
' início
threads(i).Start()
Next i
' aguarda a conclusão dos threads
For i = 0 To nbThreads - 1
threads(i).Join()
Next i ' affichage compteur
Console.Out.WriteLine(("Nombre de threads générés : " & cptrThreads))
End Sub
Public Shared Sub incrémente()
' aumenta o contador de threads
' leitura do contador
Dim valeur As Integer = cptrThreads
' acompanhamento
Console.Out.WriteLine(("A " + DateTime.Now.ToString("hh:mm:ss") & ", le thread " & Thread.CurrentThread.Name & " a lu la valeur du compteur : " & cptrThreads))
' espera
Thread.Sleep(1000)
' incremento do contador
cptrThreads = valeur + 1
' acompanhamento
Console.Out.WriteLine(("A " & DateTime.Now.ToString("hh:mm:ss") & ", le thread " & Thread.CurrentThread.Name & " a écrit la valeur du compteur : " & cptrThreads))
End Sub
End Class
Não nos deteremos na parte relativa à geração de threads, que já foi estudada. Concentremo-nos, em vez disso, no método incrémente, utilizado por cada thread para incrementar o contador estático cptrThreads.
- O contador é lido
- o thread pára durante 1 s. Assim, perde o acesso ao processador
- o contador é incrementado
A etapa 2 existe apenas para forçar a thread a perder o processador. Este será atribuído a outra thread. Na prática, nada garante que uma thread não seja interrompida entre o momento em que vai ler o contador e o momento em que vai incrementá-lo. Existe o risco de perder o processador entre o momento em que se lê o valor do contador e aquele em que se escreve o seu valor incrementado em 1. Com efeito, a operação de incremento será objeto de várias instruções elementares ao nível do processador, que podem ser interrompidas. A etapa 2 de espera de um segundo existe, portanto, apenas para sistematizar esse risco. Os resultados obtidos são os seguintes:
dos>thread3 5
A 05:44:34, le thread tache_0 a lu la valeur du compteur : 0
A 05:44:34, le thread tache_1 a lu la valeur du compteur : 0
A 05:44:34, le thread tache_2 a lu la valeur du compteur : 0
A 05:44:34, le thread tache_3 a lu la valeur du compteur : 0
A 05:44:34, le thread tache_4 a lu la valeur du compteur : 0
A 05:44:35, le thread tache_0 a écrit la valeur du compteur : 1
A 05:44:35, le thread tache_1 a écrit la valeur du compteur : 1
A 05:44:35, le thread tache_2 a écrit la valeur du compteur : 1
A 05:44:35, le thread tache_3 a écrit la valeur du compteur : 1
A 05:44:35, le thread tache_4 a écrit la valeur du compteur : 1
Nombre de threads générés : 1
Ao analisar estes resultados, percebe-se claramente o que está a acontecer:
- um primeiro thread lê o contador. Encontra o valor 0.
- Pára durante 1 s, perdendo assim o controlo do processador
- um segundo thread assume então o processador e também lê o valor do contador. Este continua a ser 0, uma vez que o thread anterior ainda não o incrementou. Este também fica parado durante 1 s.
- Em 1 s, as 5 threads têm tempo para passar todas e ler o valor 0.
- Quando forem reativados, um após o outro, irão incrementar o valor 0 que leram e escrever o valor 1 no contador, o que é confirmado pelo programa principal (Main).
De onde vem o problema? O segundo thread leu um valor errado porque o primeiro tinha sido interrompido antes de terminar o seu trabalho, que consistia em atualizar o contador na janela. Isto leva-nos ao conceito de recurso crítico e de secção crítica de um programa:
- um recurso crítico é um recurso que só pode ser detido por um thread de cada vez. Neste caso, o recurso crítico é o contador.
- Uma secção crítica de um programa é uma sequência de instruções no fluxo de execução de um thread durante a qual este acede a um recurso crítico. É necessário garantir que, durante essa secção crítica, ele seja o único a ter acesso ao recurso.
8.5. Acesso exclusivo a um recurso partilhado
No nosso exemplo, a secção crítica é o código situado entre a leitura do contador e a gravação do seu novo valor:
' leitura do contador
Dim valeur As Integer = cptrThreads
' em espera
Thread.Sleep(1000)
' incremento do contador
cptrThreads = valeur + 1
Para executar este código, é necessário garantir que uma thread esteja sozinha. Ela pode ser interrompida, mas, durante essa interrupção, nenhuma outra thread deve poder executar esse mesmo código. A plataforma .NET oferece várias ferramentas para garantir o acesso unitário às secções críticas do código. Iremos utilizar a classe Mutex:

Aqui, utilizaremos apenas os seguintes construtores e métodos:
cria um objeto de sincronização M | |
O thread T1, que executa a operação M.WaitOne(), solicita a posse do objeto de sincronização M. Se o mutex M não estiver na posse de nenhum thread (o que acontece inicialmente), este é «atribuído» ao thread T1 que o solicitou. Se, pouco tempo depois, um thread T2 realizar a mesma operação, ficará bloqueado. Com efeito, um mutex só pode pertencer a um único thread. Será desbloqueado quando o thread T1 libertar o mutex M que detém. Assim, vários threads podem ficar bloqueados à espera do mutex M. | |
A thread T1, que executa a operação M.ReleaseMutex(), abdica da posse do mutex M.Lorsque; a thread T1 perderá o processador, o sistema poderá atribuí-lo a uma das threads em espera do mutex M. Apenas uma delas o obterá por sua vez, ficando as outras em espera do M bloqueadas |
Um mutex M gere o acesso a um recurso partilhado R. Um thread solicita o recurso R através de M.WaitOne() e devolve-o através de M.ReleaseMutex(). Uma secção crítica de código que só deve ser executada por um único thread de cada vez é um recurso partilhado. A sincronização da execução da secção crítica pode ser feita da seguinte forma:
onde M é um objeto Mutex. É claro que nunca se deve esquecer de libertar um Mutex que já não seja necessário, para que outro thread possa entrar na secção crítica; caso contrário, os threads que aguardam um mutex que nunca foi libertado nunca terão acesso ao processador. Além disso, é necessário evitar a situação de interbloqueio (deadlock) em que duas threads esperam uma pela outra. Consideremos as seguintes ações que se sucedem no tempo:
- um thread T1 obtém a posse de um mutex M1 para ter acesso a um recurso partilhado R1
- um thread T2 obtém a posse de um mutex M2 para aceder a um recurso partilhado R2
- o thread T1 solicita o mutex M2. Fica bloqueado.
- O thread T2 solicita o mutex M1. Está bloqueado.
Neste caso, os threads T1 e T2 estão à espera um do outro. Esta situação ocorre quando os threads necessitam de dois recursos partilhados: o recurso R1, controlado pelo mutex M1, e o recurso R2, controlado pelo mutex M2. Uma solução possível consiste em solicitar ambas as recursos simultaneamente através de um único mutex M. No entanto, isso nem sempre é possível se, por exemplo, implicar uma ocupação prolongada de um recurso dispendioso. Outra solução consiste em que um thread que possua M1 e não consiga obter M2 liberte então M1 para evitar o interbloqueio. Se aplicarmos na prática o que acabámos de ver no exemplo anterior, a nossa aplicação fica da seguinte forma:
' opções
Option Explicit On
Option Strict On
' utilização de threads
Imports System
Imports System.Threading
Public Class thread4
' variáveis de classe
Private Shared cptrThreads As Integer = 0 ' compteur de threads
Private Shared autorisation As Mutex
Public Overloads Shared Sub Main(ByVal args() As [String])
' manual de instruções
Const syntaxe As String = "pg nbThreads"
Const nbMaxThreads As Integer = 100
' verificação do número de argumentos
If args.Length <> 1 Then
' erro
Console.Error.WriteLine(syntaxe)
' paragem
Environment.Exit(1)
End If
' verificação da qualidade do argumento
Dim nbThreads As Integer = 0
Try
nbThreads = Integer.Parse(args(0))
If nbThreads < 1 Or nbThreads > nbMaxThreads Then
Throw New Exception
End If
Catch
End Try
' inicialização da autorização de acesso a uma secção crítica
autorisation = New Mutex
' criação e geração de threads
Dim threads(nbThreads) As Thread
Dim i As Integer
For i = 0 To nbThreads - 1
' criação
threads(i) = New Thread(New ThreadStart(AddressOf incrémente))
' atribuição de nomes
threads(i).Name = "tache_" & i
' início
threads(i).Start()
Next i
' aguardar a conclusão dos threads
For i = 0 To nbThreads - 1
threads(i).Join()
Next i
' exibição do contador
Console.Out.WriteLine(("Nombre de threads générés : " & cptrThreads))
End Sub
Public Shared Sub incrémente()
' aumenta o contador de threads
' solicita-se autorização para entrar na secção crítica
autorisation.WaitOne()
' leitura do contador
Dim valeur As Integer = cptrThreads
' acompanhamento
Console.Out.WriteLine(("A " & DateTime.Now.ToString("hh:mm:ss") & ", le thread " & Thread.CurrentThread.Name & " a lu la valeur du compteur : " & cptrThreads))
' espera
Thread.Sleep(1000)
' incremento do contador
cptrThreads = valeur + 1
' acompanhamento
Console.Out.WriteLine(("A " & DateTime.Now.ToString("hh:mm:ss") & ", le thread " & Thread.CurrentThread.Name & " a écrit la valeur du compteur : " & cptrThreads))
' conceder autorização de acesso
autorisation.ReleaseMutex()
End Sub
End Class
Os resultados obtidos estão de acordo com o esperado:
dos>thread4 5
A 05:51:10, le thread tache_0 a lu la valeur du compteur : 0
A 05:51:11, le thread tache_0 a écrit la valeur du compteur : 1
A 05:51:11, le thread tache_1 a lu la valeur du compteur : 1
A 05:51:12, le thread tache_1 a écrit la valeur du compteur : 2
A 05:51:12, le thread tache_2 a lu la valeur du compteur : 2
A 05:51:13, le thread tache_2 a écrit la valeur du compteur : 3
A 05:51:13, le thread tache_3 a lu la valeur du compteur : 3
A 05:51:14, le thread tache_3 a écrit la valeur du compteur : 4
A 05:51:14, le thread tache_4 a lu la valeur du compteur : 4
A 05:51:15, le thread tache_4 a écrit la valeur du compteur : 5
Nombre de threads générés : 5
8.6. Sincronização por eventos
Consideremos a seguinte situação, por vezes designada por situação de produtores-consumidores.
- Temos uma tabela na qual alguns processos inserem dados (os produtores) e outros os leem (os consumidores).
- Os produtores são iguais entre si, mas exclusivos: apenas um produtor de cada vez pode depositar os seus dados na tabela.
- Os consumidores são iguais entre si, mas exclusivos: apenas um leitor de cada vez pode ler os dados depositados na tabela.
- Um consumidor só pode ler os dados da tabela quando um produtor os tiver depositado nela e um produtor só pode depositar novos dados na tabela quando os que aí se encontram tiverem sido consumidos.
Nesta exposição, é possível distinguir dois recursos partilhados:
- a tabela em modo de gravação
- a tabela de leitura
O acesso a estes dois recursos partilhados pode ser controlado por mutexes, tal como visto anteriormente, um para cada recurso. Assim que um consumidor obtiver a tabela em modo de leitura, deve verificar se existem efetivamente dados na mesma. Utilizar-se-á um evento para o notificar. Da mesma forma, um produtor que tenha obtido a tabela em modo de escrita deverá aguardar que um consumidor a tenha esvaziado. Também neste caso, utilizar-se-á um evento.
Os eventos utilizados farão parte da classe AutoResetEvent:

Este tipo de evento é análogo a um valor booleano, mas evita esperas ativas ou semi-ativas. Assim, se o direito de escrita for controlado por um valor booleano peutEcrire, um produtor, antes de escrever, executará um código do tipo:
ou
No primeiro método, o thread ocupa desnecessariamente o processador. No segundo, verifica o estado da variável booleana peutEcrire a cada 100 ms. A classe AutoResetEvent permite melhorar ainda mais a situação: o thread irá solicitar que seja acordado quando o evento que aguarda tiver ocorrido:
AutoEvent peutEcrire=new AutoResetEvent(false) ' peutEcrire=false;
....
peutEcrire.WaitOne() ' le thread attend que l'évt peutEcrire passe à vrai
A operação
inicializa a variável booleana peutEcrire com o valor false. A operação
executada por um thread faz com que este avance se a variável booleana peutEcrire for verdadeira; caso contrário, fica bloqueado até que se torne verdadeira. Outra thread irá defini-lo como verdadeiro através da operação peutEcrire.Set() ou como falso através da operação peutEcrire.Reset().
O programa de produtores-consumidores é o seguinte:
' utilização de threads de leitura e escrita
' ilustra a utilização simultânea de recursos partilhados e de sincronização
' opções
Option Explicit On
Option Strict On
' utilização de threads
Imports System
Imports System.Threading
Public Class lececr
' variáveis de classe
Private Shared data(5) As Integer ' ressource partagée entre threads lecteur et threads écrivain
Private Shared lecteur As Mutex ' variable de synchronisation pour lire le tableau
Private Shared écrivain As Mutex ' variable de synchronisation pour écrire dans le tableau
Private Shared objRandom As New Random(DateTime.Now.Second) ' un générateur de nombres aléatoires
Private Shared peutLire As AutoResetEvent ' signale qu'on peut lire le contenu de data
Private Shared peutEcrire As AutoResetEvent
Public Shared Sub Main(ByVal args() As [String])
' o número de threads a gerar
Const nbThreads As Integer = 3
' inicialização dos indicadores
peutLire = New AutoResetEvent(False) ' on ne peut pas encore lire
peutEcrire = New AutoResetEvent(True) ' on peut déjà écrire
' inicialização das variáveis de sincronização
lecteur = New Mutex ' synchronise les lecteurs
écrivain = New Mutex ' synchronise les écrivains
' criação dos threads de leitura
Dim lecteurs(nbThreads) As Thread
Dim i As Integer
For i = 0 To nbThreads - 1
' criação
lecteurs(i) = New Thread(New ThreadStart(AddressOf lire))
lecteurs(i).Name = "lecteur_" & i
' início
lecteurs(i).Start()
Next i
' criação dos threads de gravação
Dim écrivains(nbThreads) As Thread
For i = 0 To nbThreads - 1
' criação
écrivains(i) = New Thread(New ThreadStart(AddressOf écrire))
écrivains(i).Name = "écrivain_" & i
' início
écrivains(i).Start()
Next i
'fim da mão
Console.Out.WriteLine("fin de Main...")
End Sub
' ler o conteúdo da tabela
Public Shared Sub lire()
' secção crítica
lecteur.WaitOne() ' un seul lecteur peut passer
peutLire.WaitOne() ' on doit pouvoir lire
' leitura da tabela
Dim i As Integer
For i = 0 To data.Length - 1
'espera de 1 s
Thread.Sleep(1000)
' visualização
Console.Out.WriteLine((DateTime.Now.ToString("hh:mm:ss") & " : Le lecteur " & Thread.CurrentThread.Name & " a lu le nombre " & data(i)))
Next i
' já não é possível ler
peutLire.Reset()
' é possível escrever
peutEcrire.Set()
' fim da secção crítica
lecteur.ReleaseMutex()
End Sub
' escrever na tabela
Public Shared Sub écrire()
' secção crítica
' apenas um escritor pode passar
écrivain.WaitOne()
' é necessário aguardar a autorização de escrita
peutEcrire.WaitOne()
' gravação na tabela
Dim i As Integer
For i = 0 To data.Length - 1
'espera de 1 s
Thread.Sleep(1000)
' exibição
data(i) = objRandom.Next(0, 1000)
Console.Out.WriteLine((DateTime.Now.ToString("hh:mm:ss") & " : L'écrivain " & Thread.CurrentThread.Name & " a écrit le nombre " & data(i)))
Next i
' já não é possível escrever
peutEcrire.Reset()
' é possível ler
peutLire.Set()
'fim da secção crítica
écrivain.ReleaseMutex()
End Sub
End Class
A execução produz os seguintes resultados:
dos>lececr
fin de Main...
05:56:56 : L'écrivain écrivain_0 a écrit le nombre 459
05:56:57 : L'écrivain écrivain_0 a écrit le nombre 955
05:56:58 : L'écrivain écrivain_0 a écrit le nombre 212
05:56:59 : L'écrivain écrivain_0 a écrit le nombre 297
05:57:00 : L'écrivain écrivain_0 a écrit le nombre 37
05:57:01 : L'écrivain écrivain_0 a écrit le nombre 623
05:57:02 : Le lecteur lecteur_0 a lu le nombre 459
05:57:03 : Le lecteur lecteur_0 a lu le nombre 955
05:57:04 : Le lecteur lecteur_0 a lu le nombre 212
05:57:05 : Le lecteur lecteur_0 a lu le nombre 297
05:57:06 : Le lecteur lecteur_0 a lu le nombre 37
05:57:07 : Le lecteur lecteur_0 a lu le nombre 623
05:57:08 : L'écrivain écrivain_1 a écrit le nombre 549
05:57:09 : L'écrivain écrivain_1 a écrit le nombre 34
05:57:10 : L'écrivain écrivain_1 a écrit le nombre 781
05:57:11 : L'écrivain écrivain_1 a écrit le nombre 555
05:57:12 : L'écrivain écrivain_1 a écrit le nombre 812
05:57:13 : L'écrivain écrivain_1 a écrit le nombre 406
05:57:14 : Le lecteur lecteur_1 a lu le nombre 549
05:57:15 : Le lecteur lecteur_1 a lu le nombre 34
05:57:16 : Le lecteur lecteur_1 a lu le nombre 781
05:57:17 : Le lecteur lecteur_1 a lu le nombre 555
05:57:18 : Le lecteur lecteur_1 a lu le nombre 812
05:57:19 : Le lecteur lecteur_1 a lu le nombre 406
05:57:20 : L'écrivain écrivain_2 a écrit le nombre 442
05:57:21 : L'écrivain écrivain_2 a écrit le nombre 83
^C
É possível observar os seguintes pontos:
- existe efetivamente apenas um leitor de cada vez, embora este perca o processador na secção crítica lire
- existe efetivamente apenas um gravador de cada vez, embora este perca o processador na secção crítica écrire
- um leitor só lê quando há algo para ler na tabela
- um gravador só grava quando a tabela tiver sido lida na íntegra

