8. Os threads de execução
8.1. Introdução
Quando se inicia um aplicativo, ele é executado em um fluxo de execução chamado thread. A classe .NET, que modela um thread, é a classe System.Threading.Thread e possui a seguinte definição:

Utilizaremos apenas algumas das propriedades e métodos dessa 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 por n milissegundos | |
suspende temporariamente a execução de um thread | |
retoma a execução de um thread suspenso | |
operação bloqueante — aguarda o término do thread para passar para a instrução seguinte |
Vejamos uma primeira aplicação que destaca a existência de um thread principal de execução, aquele no qual é executada a função Main de uma classe:
' uso 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")))
' parada temporária
Thread.Sleep(1000)
End While
End Sub
End Module
Resultados na tela:
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 em um thread
- é possível acessar as características desse thread por meio de Thread.CurrentThread
- a função do método Sleep. Aqui, a thread que executa Main entra em suspensão regularmente por 1 segundo entre duas exibições.
8.2. Criação de threads de execução
É possível ter aplicativos em que trechos de código são executados de forma “simultânea” em diferentes threads de execução. Quando se diz que os threads são executados simultaneamente, muitas vezes comete-se um equívoco de linguagem. Se a máquina tiver apenas um processador, como ainda é comum, os threads compartilham esse processador: cada um deles tem acesso a ele, por sua vez, por um breve instante (alguns milissegundos). É isso que dá a ilusão de paralelismo de execução. O tempo concedido a um thread depende de vários fatores, entre os quais sua prioridade, que tem um valor padrão, mas que também pode ser definida por programação. Quando um thread dispõe do processador, ele normalmente o utiliza durante todo o tempo que lhe foi concedido. No entanto, ele pode liberá-lo antes do tempo:
- entrando em espera por um evento (wait, join, suspend)
- entrando em modo de suspensão por um período determinado (sleep)
- Um thread T é inicialmente criado por 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 ao iniciar o 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 término da tarefa T: ele passa imediatamente para a instrução seguinte. Temos, então, duas tarefas sendo executadas em paralelo. Frequentemente, elas precisam se comunicar entre si para saber em que ponto está o trabalho comum a ser realizado. Esse é o problema da sincronização de threads.
- Uma vez iniciado, o thread é executado de forma autônoma. Ele será interrompido quando a função start que ele está executando tiver concluído seu trabalho.
- É possível enviar certos sinais à tarefa T:
- T.Suspend() instrui-a a parar momentaneamente
- T.Resume() instrui-a a retomar seu trabalho
- T.Abort() instrui-a a parar definitivamente
- Também é possível aguardar o término de sua execução por meio 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 seu trabalho. Essa é uma forma de sincronização.
Vamos examinar 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
' atribuição de 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 da thread i
tâches(i) = New Thread(New ThreadStart(AddressOf affiche))
' definindo 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 por 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 outros 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
Esses resultados são muito esclarecedores:
- vemos, em primeiro lugar, 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 concluiu sua execução antes deles. A operação
inicia a execução do thread tarefas[i], mas, feito isso, a execução prossegue imediatamente com a instrução seguinte, sem aguardar o término da execução do thread.
- Todas as threads criadas devem executar o método affiche. A ordem de execução é imprevisível. Mesmo que, no exemplo, a ordem de execução pareça seguir a ordem das solicitações de execução, não se pode tirar conclusões gerais a partir disso. O sistema operacional possui, neste caso, 6 threads e um processador. Ele distribuirá o processador entre essas 6 threads de acordo com regras próprias.
- Nos resultados, observa-se uma consequência do método Sleep. No exemplo, é o thread 0 que executa primeiro o método affiche. A mensagem de início de execução é exibida e, em seguida, ele executa o método Sleep, que o suspende por 1 segundo. Ele então perde o processador, que fica disponível para outro thread. O exemplo mostra que é o thread 1 que o obterá. A thread 1 seguirá o mesmo caminho, assim como as outras threads. Quando o segundo de espera da thread 0 terminar, sua execução poderá ser retomada. O sistema lhe concede o processador e ela pode concluir a execução do método affiche.
Vamos modificar nosso programa para encerrar 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 isso: ela encerra todas as threads do aplicativo e não apenas a thread Main. A solução para esse problema é que o método Main aguarde o término da execução dos threads que ele criou antes de encerrar a si mesmo. Isso pode ser feito com o método Join da classe Thread:
' aguarda o término da execução de todas as threads
For i = 0 To tâches.Length - 1
' aguardando o fim da execução da thread i
tâches(i).Join()
Next i 'for
' fim da rotina
Console.Out.WriteLine(("fin du thread " + main.Name))
Environment.Exit(0)
Assim, obtêm-se 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 destacamos a existência de um thread padrão — aquele que executa o método Main — e que sabemos como criar outros, vamos nos deter na utilidade dos threads para nós e no motivo pelo qual os apresentamos aqui. Existe um tipo de aplicativo que se adapta bem ao uso de threads: os aplicativos cliente-servidor da Internet. Nesse tipo de aplicação, um servidor localizado em uma máquina S1 responde às solicitações de clientes localizados em máquinas remotas C1, C2, ..., Cn.
![]() |
Todos os dias utilizamos aplicações da Internet que se enquadram nesse esquema: serviços da Web, e-mail, consulta de fóruns, transferência de arquivos... No esquema acima, o servidor S1 deve atender os clientes Ci simultaneamente. Se tomarmos o exemplo de um servidor FTP (Protocolo de Transferência de Arquivos) que fornece arquivos aos seus clientes, sabemos que uma transferência de arquivo pode, às vezes, levar várias horas. É claro que está fora de questão que um único cliente monopolize o servidor por todo esse tempo. O que se faz normalmente é que o servidor crie 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 é compartilhado ciclicamente entre todos os threads ativos da máquina, o servidor dedica um pouco de tempo a cada cliente, garantindo assim a simultaneidade do serviço.
![]() |
8.4. Acesso a recursos compartilhados
No exemplo cliente-servidor mencionado acima, cada thread atende a um cliente de forma amplamente independente. No entanto, as threads podem ser levadas a cooperar para prestar o serviço solicitado ao seu cliente, especialmente no que diz respeito ao acesso a recursos compartilhados. O esquema acima lembra os guichês de um grande órgão público, como uma agência dos correios, por exemplo, onde, em cada guichê, um funcionário atende um cliente. Suponhamos que, de vez em quando, esses funcionários precisem fazer fotocópias de documentos trazidos por seus clientes e que haja apenas uma fotocopiadora. Dois funcionários não podem usar a fotocopiadora ao mesmo tempo. Se o funcionário i encontrar a fotocopiadora sendo usada pelo funcionário j, ele terá que esperar. Essa situação é chamada de acesso a um recurso compartilhado e, na informática, é bastante delicada de se gerenciar. Vejamos o seguinte exemplo:
- um aplicativo irá gerar n threads, sendo que n é passado como parâmetro
- o recurso compartilhado é um contador que deverá ser incrementado por cada thread gerado
- Ao final da execução do programa, o valor do contador é exibido. Portanto, deveríamos encontrar n.
O programa é o seguinte:
' opções
Option Explicit On
Option Strict On
' uso 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)
' interrupção
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))
' nomeação
threads(i).Name = "tache_" & i
' inicialização
threads(i).Start()
Next i
' aguardando o término 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()
' incrementa 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. Vamos nos concentrar, em vez disso, no método incrémente, utilizado por cada thread para incrementar o contador estático cptrThreads.
- o contador é lido
- a thread fica parada por 1 s. Assim, ela perde o processador
- o contador é incrementado
A etapa 2 existe apenas para forçar a thread a perder o processador. Este será cedido a outra thread. Na prática, nada garante que uma thread não seja interrompida entre o momento em que ela lerá o contador e o momento em que o incrementará. Existe o risco de perder o processador entre o momento em que se lê o valor do contador e aquele em que se grava seu valor incrementado em 1. De fato, a operação de incremento será composta por várias instruções elementares no nível do processador, que podem ser interrompidas. A etapa 2, com um segundo de espera, 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 esses resultados, fica claro o que está acontecendo:
- um primeiro thread lê o contador. Ele encontra 0.
- Ele fica parado por 1 s, perdendo assim o controle do processador
- um segundo thread, então, assume o processador e também lê o valor do contador. Ele ainda está em 0, já que o thread anterior ainda não o incrementou. Ele também fica parado por 1 s.
- Em 1 s, as 5 threads têm tempo de passar todas e ler o valor 0.
- Quando forem reativados, um após o outro, eles irão incrementar o valor 0 que leram e gravar o valor 1 no contador, o que é confirmado pelo programa principal (Main).
De onde vem o problema? O segundo thread leu um valor incorreto porque o primeiro foi interrompido antes de concluir seu trabalho, que era atualizar o contador na janela. Isso nos leva ao conceito de recurso crítico e de seção crítica de um programa:
- um recurso crítico é um recurso que só pode ser mantido por um thread por vez. Aqui, o recurso crítico é o contador.
- uma seção crítica de um programa é uma sequência de instruções no fluxo de execução de um thread durante a qual ele acessa um recurso crítico. É preciso garantir que, durante essa seção crítica, ele seja o único a ter acesso ao recurso.
8.5. Acesso exclusivo a um recurso compartilhado
Em nosso exemplo, a seção crítica é o código localizado entre a leitura do contador e a gravação de seu novo valor:
' leitura do contador
Dim valeur As Integer = cptrThreads
' em espera
Thread.Sleep(1000)
' incremento do contador
cptrThreads = valeur + 1
Para executar esse 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 exclusivo às seções críticas do código. Usaremos a classe Mutex:

Aqui, utilizaremos apenas os seguintes construtores e métodos:
cria um objeto de sincronização M | |
A 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 sob a posse de nenhuma thread (o que ocorre inicialmente), ele é “concedido” ao thread T1 que o solicitou. Se, pouco depois, um thread T2 realizar a mesma operação, ele ficará bloqueado. De fato, um mutex só pode pertencer a uma thread. Ele será desbloqueado quando a thread T1 liberar o mutex M que está detendo. Assim, várias threads podem ficar bloqueadas aguardando o mutex M. | |
A thread T1, que executa a operação M.ReleaseMutex(), libera a posse do mutex M.Lorsque; a thread T1 perderá o processador, o sistema poderá atribuí-lo a um dos threads em espera pelo mutex M. Apenas um deles o obterá por sua vez; os demais, que aguardam o mutex M, permanecerão bloqueados |
Um mutex M gerencia o acesso a um recurso compartilhado R. Uma thread solicita o recurso R por meio de M.WaitOne() e o libera por meio de M.ReleaseMutex(). Uma seção crítica de código que deve ser executada por apenas uma thread por vez é um recurso compartilhado. A sincronização da execução da seção crítica pode ser feita da seguinte forma:
onde M é um objeto Mutex. É claro que nunca se deve esquecer de liberar um Mutex que se tornou desnecessário para que outra thread possa entrar na seção crítica; caso contrário, as threads que aguardam um mutex que nunca foi liberado nunca terão acesso ao processador. Além disso, é preciso evitar a situação de interbloqueio (deadlock) na qual duas threads aguardam uma à outra. Consideremos as seguintes ações que ocorrem sequencialmente no tempo:
- um thread T1 obtém a posse de um mutex M1 para ter acesso a um recurso compartilhado R1
- um thread T2 obtém a posse de um mutex M2 para acessar um recurso compartilhado R2
- o thread T1 solicita o mutex M2. Ele fica bloqueado.
- O thread T2 está solicitando o mutex M1. Ele está bloqueado.
Nesse caso, as threads T1 e T2 estão aguardando uma à outra. Essa situação ocorre quando os threads precisam de dois recursos compartilhados: o recurso R1, controlado pelo mutex M1, e o recurso R2, controlado pelo mutex M2. Uma solução possível é solicitar os dois recursos ao mesmo tempo por meio de um único mutex M. Mas isso nem sempre é possível se, por exemplo, isso implicar em uma ocupação prolongada de um recurso caro. Outra solução é que um thread que possua M1 e não consiga obter M2 libere, então, M1 para evitar o interbloqueio. Se colocarmos em prática o que acabamos de ver no exemplo anterior, nossa aplicação fica da seguinte forma:
' opções
Option Explicit On
Option Strict On
' uso 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)
' interrupção
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 seçã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))
' nomeação
threads(i).Name = "tache_" & i
' inicialização
threads(i).Start()
Next i
' aguardando 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()
' incrementa o contador de threads
' solicita-se autorização para entrar na seçã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 permissã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, às vezes chamada de 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 por vez pode inserir seus dados na tabela.
- Os consumidores são iguais entre si, mas exclusivos: apenas um leitor por vez pode ler os dados armazenados na tabela.
- Um consumidor só pode ler os dados da tabela quando um produtor os tiver gravado nela, e um produtor só pode gravar novos dados na tabela quando os que já estão nela tiverem sido consumidos.
Nesta exposição, é possível distinguir dois recursos compartilhados:
- a tabela para gravação
- a tabela em leitura
O acesso a esses dois recursos compartilhados pode ser controlado por mutexes, conforme visto anteriormente, um para cada recurso. Assim que um consumidor obtiver a tabela em modo de leitura, ele deve verificar se há realmente dados nela. Utilizaremos um evento para notificá-lo. Da mesma forma, um produtor que tenha obtido a tabela em modo de gravação deverá aguardar até que um consumidor a tenha esvaziado. Utilizaremos, mais uma vez, um evento.
Os eventos utilizados farão parte da classe AutoResetEvent:

Esse tipo de evento é análogo a um booleano, mas evita esperas ativas ou semi-ativas. Assim, se o direito de gravação for controlado por um booleano peutEcrire, um produtor, antes de gravar, executará um código do tipo:
ou
No primeiro método, o thread sobrecarrega desnecessariamente o processador. No segundo, ele verifica o estado da variável booleana peutEcrire a cada 100 ms. A classe AutoResetEvent permite melhorar ainda mais a situação: o thread solicitará ser acordado quando o evento que ele aguarda ocorrer:
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 ela se torne verdadeira. Outra thread o definirá como verdadeiro por meio da operação peutEcrire.Set() ou como falso por meio da operação peutEcrire.Reset().
O programa de produtores-consumidores é o seguinte:
' uso de threads de leitura e gravação
' ilustra o uso simultâneo de recursos compartilhados e sincronização
' opções
Option Explicit On
Option Strict On
' uso 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 serem geradas
Const nbThreads As Integer = 3
' inicialização dos sinalizadores
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
' inicialização
lecteurs(i).Start()
Next i
' criação das 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
' inicialização
é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()
' seçã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)
' exibição
Console.Out.WriteLine((DateTime.Now.ToString("hh:mm:ss") & " : Le lecteur " & Thread.CurrentThread.Name & " a lu le nombre " & data(i)))
Next i
' não é mais possível ler
peutLire.Reset()
' é possível gravar
peutEcrire.Set()
' fim da seção crítica
lecteur.ReleaseMutex()
End Sub
' gravar na tabela
Public Shared Sub écrire()
' seção crítica
' apenas um escritor pode passar
écrivain.WaitOne()
' é preciso aguardar a autorização para gravar
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
' não é mais possível gravar
peutEcrire.Reset()
' é possível ler
peutLire.Set()
'fim da seçã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:
- há de fato apenas um leitor por vez, embora este perca o processador na seção crítica lire
- há de fato apenas um gravador por vez, embora este perca o processador na seçã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 por completo

