Comme mentionné précédemment, chaque thread possède ses propres ressources, mais la zone de code est partagée, c'est-à-dire que chaque thread peut exécuter la même fonction. Le problème que cela peut causer est que plusieurs threads exécutent une fonction en même temps, provoquant une confusion des données et des résultats imprévisibles, nous devons donc éviter cette situation.
C# fournit un verrou par mot clé, qui peut définir une section de code comme une section mutuellement exclusive (section critique). La section mutuellement exclusive permet à un seul thread d'entrer en exécution à la fois, tandis que les autres threads doivent attendre. En C#, le verrouillage par mot clé est défini comme suit :
verrouiller (expression) bloc_instruction
L'expression représente l'objet que vous souhaitez suivre, généralement une référence d'objet.
Le Statement_block est le code de la section mutuellement exclusive. Ce code ne peut être exécuté que par un seul thread à la fois.
Ce qui suit est un exemple typique d'utilisation du mot-clé lock. L'utilisation et le but du mot-clé lock sont expliqués dans les commentaires.
Les exemples sont les suivants :
Code
utiliser le système ;
en utilisant System.Threading ;
espace de noms ThreadSimple
{
Compte de classe interne
{
solde international ;
Aléatoire r = nouveau Aléatoire();
Compte interne (int initial)
{
solde = initial ;
}
interne int Retrait (montant int)
{
si (solde < 0)
{
//Si le solde est inférieur à 0, lève une exception
throw new Exception("Solde négatif");
}
//Le code suivant est garanti avant que le thread actuel ne modifie la valeur de balance.
//Aucun autre thread n'exécutera ce code pour modifier la valeur du solde
//Par conséquent, la valeur du solde ne peut pas être inférieure à 0
verrouiller (ceci)
{
Console.WriteLine("Thème actuel :"+Thread.CurrentThread.Name);
//S'il n'y a pas de protection du mot-clé lock, cela peut être après l'exécution du jugement conditionnel if.
//Un autre thread a exécuté balance=balance-amount et a modifié la valeur de balance.
//Cette modification est invisible pour ce thread, elle peut donc faire en sorte que la condition if ne soit plus valable.
//Cependant, ce thread continue d'exécuter balance=balance-amount, donc le solde peut être inférieur à 0
si (solde >= montant)
{
Thread.Sleep(5);
solde = solde - montant ;
montant du retour ;
}
autre
{
renvoie 0 ; // transaction rejetée
}
}
}
vide interne DoTransactions()
{
pour (int i = 0; i < 100; i++)
Retirer(r.Suivant(-50, 100));
}
}
Test de classe interne
{
Thread interne statique[] threads = new Thread[10];
public static void Main()
{
Compte acc = nouveau compte (0);
pour (int i = 0; i < 10; i++)
{
Fil t = nouveau fil (nouveau ThreadStart (acc.DoTransactions));
fils[i] = t;
}
pour (int i = 0; i < 10; i++)
threads[i].Name=i.ToString();
pour (int i = 0; i < 10; i++)
fils de discussion[i].Start();
Console.ReadLine();
}
}
}La classe Monitor verrouille un objet
Lorsque plusieurs threads partagent un objet, des problèmes similaires au code commun se produiront également. Pour ce type de problème, le mot-clé lock ne doit pas être utilisé ici. , Monitor fournit une solution permettant aux threads de partager des ressources.
La classe Monitor peut verrouiller un objet et un thread ne peut opérer sur l'objet que s'il obtient le verrou. Le mécanisme de verrouillage d'objet garantit qu'un seul thread peut accéder à cet objet à la fois dans des situations susceptibles de provoquer le chaos. Monitor doit être associé à un objet spécifique, mais comme il s'agit d'une classe statique, il ne peut pas être utilisé pour définir des objets et toutes ses méthodes sont statiques et ne peuvent pas être référencées à l'aide d'objets. Le code suivant illustre l'utilisation de Monitor pour verrouiller un objet :
...
File d'attente oQueue=nouvelle file d'attente();
...
Monitor.Enter(oQueue);
......//Maintenant, l'objet oQueue ne peut être manipulé que par le thread actuel
Monitor.Exit(oQueue);//Libérez le verrou
Comme indiqué ci-dessus, lorsqu'un thread appelle la méthode Monitor.Enter() pour verrouiller un objet, l'objet lui appartient. Si d'autres threads souhaitent accéder à cet objet, ils ne peuvent qu'attendre qu'il utilise Monitor.Exit(). méthode pour libérer le verrou. Afin de garantir que le thread finira par libérer le verrou, vous pouvez écrire la méthode Monitor.Exit() dans le bloc de code final de la structure try-catch-finally.
Pour tout objet verrouillé par Monitor, certaines informations qui lui sont liées sont stockées en mémoire :
L'une est une référence au thread qui détient actuellement le verrou ;
La seconde est une file d'attente de préparation, qui stocke les threads prêts à acquérir des verrous ;
La troisième est une file d'attente, qui contient une référence à la file d'attente qui attend actuellement que le statut de l'objet change.
Lorsque le thread qui possède le verrou d'objet est prêt à libérer le verrou, il utilise la méthode Monitor.Pulse() pour avertir le premier thread de la file d'attente, afin que le thread soit transféré vers la file d'attente de préparation lorsque le verrou d'objet est libéré. , dans la file d'attente de préparation Le thread peut immédiatement acquérir le verrou d'objet.
Ce qui suit est un exemple montrant comment utiliser le mot-clé lock et la classe Monitor pour implémenter la synchronisation et la communication des threads. Il s'agit également d'un problème typique de producteur et de consommateur.
Dans cette routine, le fil producteur et le fil consommateur alternent. Le producteur écrit un numéro, et le consommateur le lit et l'affiche immédiatement (l'essence du programme est présentée dans les commentaires).
Les espaces de noms système utilisés sont les suivants :
utiliser le système ;
en utilisant System.Threading ;
Tout d'abord, définissez une classe Cell de l'objet sur lequel l'opération est effectuée. Dans cette classe, il existe deux méthodes : ReadFromCell() et WriteToCell. Le thread consommateur appellera ReadFromCell() pour lire le contenu de cellContents et l'afficher, et le processus producteur appellera la méthode WriteToCell() pour écrire des données dans cellContents.
Les exemples sont les suivants :
Code
Cellule de classe publique
{
int cellContents; //Contenu dans l'objet Cell
bool readerFlag = false; // Indicateur d'état, quand il est vrai, il peut être lu, lorsqu'il est faux, il écrit
public int ReadFromCell( )
{
lock(this) // Que garantit le mot-clé Lock ? Veuillez lire l'introduction précédente sur lock.
{
if (!readerFlag)//S'il n'est pas lisible maintenant
{
essayer
{
//Attendez que la méthode Monitor.Pulse() soit appelée dans la méthode WriteToCell
Monitor.Wait(this);
}
capture (SynchronizationLockException e)
{
Console.WriteLine(e);
}
attraper (ThreadInterruptedException e)
{
Console.WriteLine(e);
}
}
Console.WriteLine("Consomme : {0}",cellContents);
lecteurFlag = faux ;
// Réinitialise l'indicateur readerFlag pour indiquer que le comportement de consommation est terminé
Monitor.Pulse(ce);
//Notifie la méthode WriteToCell() (cette méthode est exécutée dans un autre thread et est en attente)
}
renvoie le contenu de la cellule ;
}
public void WriteToCell (int n)
{
verrouiller (ceci)
{
si (lecteurFlag)
{
essayer
{
Monitor.Wait(this);
}
capture (SynchronizationLockException e)
{
//Lorsque des méthodes synchronisées (faisant référence aux méthodes de la classe Monitor sauf Enter) sont appelées dans des zones de code asynchrone
Console.WriteLine(e);
}
attraper (ThreadInterruptedException e)
{
// Abandonner lorsque le thread est en état d'attente
Console.WriteLine(e);
}
}
contenu de la cellule = n ;
Console.WriteLine("Produce : {0}",cellContents);
lecteurFlag = vrai ;
Monitor.Pulse(ce);
//Notifie la méthode ReadFromCell() en attente dans un autre thread
}
}
}
La classe productrice CellProd et la classe consommateur CellCons sont définies ci-dessous. Elles n'ont toutes deux qu'une seule méthode ThreadRun(), de sorte que l'objet proxy ThreadStart fourni au thread dans la fonction Main() sert d'entrée au thread.
classe publique CellProd
{
Cellule ; // Objet cellulaire sur lequel l'opération est effectuée
int quantité = 1; // Le nombre de fois que le producteur produit, initialisé à 1
public CellProd (Cell box, requête int)
{
//Constructeur
cellule = boîte ;
quantité = demande ;
}
public void ThreadRun( )
{
pour(int boucleur=1; boucleur<=quantité; boucleur++)
cell.WriteToCell(looper); //Le producteur écrit des informations dans l'objet opération
}
}
CellCons de classe publique
{
Cellule cellulaire ;
quantité entière = 1 ;
public CellCons (Cell box, requête int)
{
//Constructeur
cellule = boîte ;
quantité = demande ;
}
public void ThreadRun( )
{
int valReturned ;
pour(int boucleur=1; boucleur<=quantité; boucleur++)
valReturned=cell.ReadFromCell();//Le consommateur lit les informations de l'objet d'opération
}
}
Ensuite, dans la fonction Main() de la classe MonitorSample ci-dessous, nous devons créer deux threads en tant que producteurs et consommateurs, et utiliser la méthode CellProd.ThreadRun() et la méthode CellCons.ThreadRun() pour effectuer des opérations sur le même. Objet cellulaire.
Code
classe publique MonitorSample
{
public static void Main (String[] arguments)
{
int result = 0; //Un bit d'indicateur S'il est 0, cela signifie qu'il n'y a pas d'erreur dans le programme. S'il est 1, cela signifie qu'il y a une erreur.
Cellule = nouvelle Cellule( );
//Ce qui suit utilise cell pour initialiser les classes CellProd et CellCons. Les temps de production et de consommation sont tous deux 20 fois.
CellProd prod = new CellProd(cellule, 20);
CellCons cons = new CellCons (cellule, 20);
Producteur de thread = new Thread(new ThreadStart(prod.ThreadRun));
Consommateur de thread = new Thread(new ThreadStart(cons.ThreadRun));
//Le thread producteur et le thread consommateur ont été créés, mais l'exécution n'a pas démarré.
essayer
{
producteur.Start( );
consommateur.Start( );
producteur.Join( );
consommateur.Join( );
Console.ReadLine();
}
attraper (ThreadStateException e)
{
//Lorsque le thread ne peut pas effectuer l'opération demandée en raison de son état
Console.WriteLine(e);
résultat = 1 ;
}
attraper (ThreadInterruptedException e)
{
// Abandonner lorsque le thread est en état d'attente
Console.WriteLine(e);
résultat = 1 ;
}
// Bien que la fonction Main() ne renvoie pas de valeur, l'instruction suivante peut renvoyer le résultat de l'exécution au processus parent
Environment.ExitCode = résultat ;
}
}
Dans la routine ci-dessus, la synchronisation s'effectue en attendant Monitor.Pulse(). Premièrement, le producteur produit une valeur, et en même temps le consommateur est dans un état d'attente jusqu'à ce qu'il reçoive une « impulsion » du producteur pour l'informer que la production est terminée. Après cela, le consommateur entre dans l'état de consommation. et le producteur commence à attendre que le consommateur termine. L'"impulsion" émise par Monitor.Pulese() sera appelée après l'opération.
Le résultat de son exécution est simple :
Produit : 1
Consommer : 1
Produit : 2
Consommer : 2
Produit : 3
Consommer : 3
...
...
Produit : 20
Consommer : 20
En fait, cet exemple simple nous a aidé à résoudre de gros problèmes pouvant survenir dans les applications multithread. Tant que vous comprenez les méthodes de base de résolution des conflits entre les threads, il est facile de l'appliquer à des programmes plus complexes.
-