donnéesPilote de base de données
5. On peut aussi attendre la fin de son exécution par T.join() On a là une instruction bloquante : le programme qui l'exécute est bloqué jusqu'à ce que la tâche T ait terminé son travail C'est un moyen de synchronisation.
7.5 Accès exclusif à une ressource partagée
Dans notre exemple, la section critique est le code situé entre la lecture du compteur et l'écriture de sa nouvelle valeur :
' lecture compteur
Dim valeur As Integer = cptrThreads ' attente
Thread.Sleep(1000)
' incrémentation compteur cptrThreads = valeur + 1
Les threads d'exécution 181 Pour exécuter ce code, un thread doit être assuré d'être tout seul. Il peut être interrompu mais pendant cette interruption, un autre thread ne doit pas pouvoir exécuter ce même code. La plate-forme .NET offre plusieurs outils pour assurer l'entrée unitaire dans les sections critiques de code. Nous utiliserons la classe Mutex :
Nous n'utiliserons ici que les constructeurs et méthodes suivants :
public Mutex() crée un objet de synchronisation M
public bool WaitOne() Le thread T1 qui exécute l'opération M.WaitOne() demande la propriété de l'objet de
synchronisation M. Si le Mutex M n'est détenu par aucun thread (le cas au départ), il est "donné" au thread T1 qui l'a demandé. Si un peu plus tard, un thread T2 fait la même opération, il sera bloqué. En effet, un Mutex ne peut appartenir qu'à un thread. Il sera débloqué lorsque le thread T1 libèrera le mutex M qu'il détient. Plusieurs threads peuvent ainsi être bloqués en attente du Mutex M.
public void
ReleaseMutex() Le thread T1 qui effectue l'opération M.ReleaseMutex() abandonne la propriété du Mutex M.Lorsque le thread T1 perdra le processeur, le système pourra le donner à l'un des threads en attente du
Mutex M. Un seul l'obtiendra à son tour, les autres en attente de M restant bloqués
Un Mutex M gère l'accès à une ressource partagée R. Un thread demande la ressource R par M.WaitOne() et la rend par M.ReleaseMutex(). Une section critique de code qui ne doit être exécutée que par un seul thread à la fois est une ressource partagée. La synchronisation d'exécution de la section critique peut se faire ainsi :
M.WaitOne()
' le thread est seul à entrer ici ' section critique
....
M.ReleaseMutex()
où M est un objet Mutex. Il faut bien sûr ne jamais oublier de libérer un Mutex devenu inutile pour qu'un autre thread puisse entrer dans la section critique, sinon les threads en attente d'un Mutex jamais libéré n'auront jamais accès au processeur. Par ailleurs, il faut éviter la situation d'interblocage (deadlock) dans laquelle deux threads s'attendent mutuellement. Considérons les actions suivantes qui se suivent dans le temps :
§ un thread T1 obtient la propriété d'un Mutex M1 pour avoir accès à une ressource partagée R1 § un thread T2 obtient la propriété d'un Mutex M2 pour avoir accès à une ressource partagée R2 § le thread T1 demande le Mutex M2. Il est bloqué.
§ le thread T2 demande le Mutex M1. Il est bloqué.
Ici, les threads T1 et T2 s'attendent mutuellement. Ce cas apparaît lorsque des threads ont besoin de deux ressources partagées, la ressource R1 contrôlée par le Mutex M1 et la ressource R2 contrôlée par le Mutex M2. Une solution possible est de demander les deux ressources en même temps à l'aide d'un Mutex unique M. Mais ce n'est pas toujours possible si par exemple cela entraîne une mobilisation longue d'une ressource coûteuse. Une autre solution est qu'un thread ayant M1 et ne pouvant obtenir M2, relâche alors M1 pour éviter l'interblocage. Si nous mettons en pratique ce que nous venons de voir sur l'exemple précédent, notre application devient la suivante : ' options Option Explicit On Option Strict On ' utilisation de threads Imports System Imports System.Threading
Public Class thread4
' variables de classe
Les threads d'exécution 182 Private Shared autorisation As Mutex
Public Overloads Shared Sub Main(ByVal args() As [String]) ' mode d'emploi
Const syntaxe As String = "pg nbThreads" Const nbMaxThreads As Integer = 100 ' vérification nbre d'arguments
If args.Length <> 1 Then ' erreur Console.Error.WriteLine(syntaxe) ' arrêt Environment.Exit(1) End If
' vérification qualité de l'argument
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
' initialisation de l'autorisation d'accès à une section critique
autorisation = New Mutex
' création et génération des threads
Dim threads(nbThreads) As Thread Dim i As Integer
For i = 0 To nbThreads - 1 ' création
threads(i) = New Thread(New ThreadStart(AddressOf incrémente)) ' nommage
threads(i).Name = "tache_" & i ' lancement
threads(i).Start() Next i
' attente de la fin des 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() ' augmente le compteur de threads
' on demande l'autorisation d'entrer dans la secton critique
autorisation.WaitOne() ' lecture compteur
Dim valeur As Integer = cptrThreads ' suivi
Console.Out.WriteLine(("A " & DateTime.Now.ToString("hh:mm:ss") & ", le thread " & Thread.CurrentThread.Name & " a lu la valeur du compteur : " & cptrThreads))
' attente
Thread.Sleep(1000)
' incrémentation compteur
cptrThreads = valeur + 1 ' suivi
Console.Out.WriteLine(("A " & DateTime.Now.ToString("hh:mm:ss") & ", le thread " & Thread.CurrentThread.Name & " a écrit la valeur du compteur : " & cptrThreads)) ' on rend l'autorisation d'accès
autorisation.ReleaseMutex()
End Sub End Class
Les résultats obtenus sont conformes à ce qui était attendu :
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
Les threads d'exécution 183