Maison > Article > développement back-end > Introduction détaillée aux exemples de code graphique de synchronisation de threads C#
Cet article présente principalement les connaissances pertinentes sur la synchronisation des threads en C#. Il a une très bonne valeur de référence, jetons-y un œil avec l'éditeur ci-dessous
Avant-propos
Lorsqu'un thread du pool de threads est bloqué, le pool de threads créera des threads supplémentaires, et la création, la destruction et la planification de threads nécessitent des ressources mémoire assez coûteuses. De plus, de nombreux développeurs ont l'habitude de créer plus de threads lorsqu'ils voient que les threads de leurs programmes ne font rien d'utile pour créer des threads évolutifs et évolutifs. programmes réactifs, nous avons présenté l'explication détaillée de la programmation asynchrone C# avant
Cependant, il existe également de sérieux problèmes dans la programmation asynchrone si deux threads différents accèdent aux mêmes variables et données, selon l'implémentation de notre fonction asynchrone, non Il peut y avoir deux threads accédant aux mêmes données en même temps. Dans ce cas, nous avons besoin d'une synchronisation des threads. Lorsque plusieurs threads accèdent aux données partagées en même temps, la synchronisation des threads peut empêcher la corruption des données. La raison pour laquelle le concept de simultanéité est mis en avant est que l'essence de la synchronisation des threads est une question de timing.
Asynchrone et synchrone sont relatifs. La synchronisation signifie une exécution séquentielle. Après en avoir exécuté une, vous devez attendre et coordonner l'exécution de la suivante. Asynchrone signifie qu'ils sont indépendants les uns des autres et continuent de faire leurs propres choses en attendant un événement. Il n'est pas nécessaire d'attendre la fin de l'événement avant de travailler à nouveau. Les threads sont un moyen de devenir asynchrone. Asynchrone signifie que le thread principal qui appelle la méthode n'a pas besoin d'attendre la fin d'un autre thread de manière synchrone, afin que le thread principal puisse faire autre chose.
Constructions primitives en mode utilisateur et en mode noyau
Concepts de base
Primitives : Oui Constructions simples utilisées dans le code
Mode utilisateur : en coordonnant les threads via des instructions spéciales du processeur, le système d'exploitation ne détecte jamais un blocage de thread sur une construction primitive en mode utilisateur.
Mode Kernel : Fournies par Windows lui-même, les fonctions implémentées par le noyau sont appelées dans le thread de l'application.
Constructions en mode utilisateur
Constructions volatiles
Le compilateur C#, le compilateur JIT et les processeurs optimiser le code, et ils essaient de s'assurer que nos intentions sont conservées. Cependant, d'un point de vue multi-thread, nos intentions peuvent ne pas être conservées :
static void Main(string[] args) { Console.WriteLine("让worker函数运行5s后停止"); var t = new Thread(Worker); t.Start(); Thread.Sleep(5000); stop = true; Console.ReadLine(); } private static bool stop = false; private static void Worker(object obj) { int x = 0; while (!stop) { x++; } Console.WriteLine("worker函数停止x={0}",x); }Si le compilateur vérifie que stop est faux, il génère du code pour entrer dans une boucle infinie et continue d'incrémenter x dans la boucle, donc la boucle d'optimisation se termine rapidement, mais le compilateur ne détecte l'arrêt qu'une seule fois , pas à chaque fois. Exemple 2---Deux threads accèdent simultanément :
class test { private static int m_flag = 0; private static int m_value = 0; public static void Thread1(object obj) { m_value = 5; m_flag = 1; } public static void Thread2(object obj) { if (m_flag == 1) Console.WriteLine("m_value = {0}", m_value); } //多核CPU机器才会出现线程同步问题 public void Exec() { var thread1 = new Thread(Thread1); var thread2 = new Thread(Thread2); thread1.Start(); thread2.Start(); Console.ReadLine(); } }Lorsque le programme est exécuté, le compilateur doit changer la variable m_flag et m_value sont lus de la RAM dans le registre du processeur. La RAM transmet d'abord la valeur de m_value 0, et thread1 change la valeur en 5, mais thread2 ne sait pas que thread2 pense toujours que la valeur est 0. De manière générale, ce problème est plus probable. se produit dans les processeurs multicœurs, plus il y a de processeurs, plus il y a de chances que plusieurs threads accèdent aux ressources en même temps. Le mot-clé volatile désactive certaines optimisations effectuées par le compilateur C#, le compilateur JTP et le CPU. S'il est appliqué à une variable, le champ ne pourra pas être mis en cache dans le registre du CPU, garantissant ainsi la lecture et l'écriture de celui-ci. le terrain est en sécurité. Faites-le dans la RAM.
Construction de verrouillage
Chaque méthode de la classe System.Threading.Interlocked effectue une opération de lecture et d'écriture atomique avant d'appeler une méthode Interlocked. Toutes les écritures de variables sont effectuées avant cet appel à la méthode Interlocked, et toutes les lectures de variables après l'appel sont effectuées après cet appel. La méthode Interlocked effectue principalement des opérations statiques sur les variables INT32 telles que Add, Decrement, Compare, Exchange, CompareChange et d'autres méthodes, et accepte également les paramètres d'objet, Double et d'autres types. Opération atomique : fait référence à une opération qui ne sera pas interrompue par le mécanisme de planification des threads ; une fois cette opération démarrée, elle s'exécutera jusqu'à la fin sans aucun changement de contexte (passage à un autre thread) au milieu. Démonstration de code : Description : interrogez de manière asynchrone plusieurs serveurs Web via la méthode Interlocked et renvoyez des données en même temps, et les résultats ne sont exécutés qu'une seule fois.//上报状态类型 enum CoordinationStatus { Cancel, Timeout, AllDone }
class AsyncCoordinator { //AllBegun 内部调用JustEnded来递减它 private int _mOpCount = 1; //0=false,1=true private int _mStatusReported = 0; private Action<CoordinationStatus> _mCallback; private Timer _mTimer; //发起一个操作之前调用 public void AboutToBegin(int opsToAdd = 1) { Interlocked.Add(ref _mOpCount, opsToAdd); } //处理好一个操作的结果之后调用 public void JustEnded() { if (Interlocked.Decrement(ref _mOpCount) == 0) { ReportStatus(CoordinationStatus.AllDone); } } //该方法必须在发起所有操作后调用 public void AllBegin(Action<CoordinationStatus> callback, int timeout = Timeout.Infinite) { _mCallback = callback; if (timeout != Timeout.Infinite) { _mTimer = new Timer(TimeExpired, null, timeout, Timeout.Infinite); JustEnded(); } } private void TimeExpired(object o) { ReportStatus(CoordinationStatus.Timeout); } public void Cancel() { ReportStatus(CoordinationStatus.Cancel); } private void ReportStatus(CoordinationStatus status) { //如果状态从未报告过,就报告它,否则就忽略它,只调用一次 if (Interlocked.Exchange(ref _mStatusReported, 1) == 0) { _mCallback(status); } } }
class MultiWebRequest { //辅助类 用于协调所有的异步操作 private AsyncCoordinator _mac = new AsyncCoordinator(); protected Dictionary<string,object> _mServers = new Dictionary<string, object> { {"http://www.baidu.com",null},{"http://www.Microsoft.com",null},{"http://www.cctv.com",null}, {"http://www.souhu.com",null},{"http://www.sina.com",null},{"http://www.tencent.com",null}, {"http://www.youku.com",null} }; private Stopwatch sp; public MultiWebRequest(int timeout = Timeout.Infinite) { sp = new Stopwatch(); sp.Start(); //通过异步方式一次性发起请求 var httpclient = new HttpClient(); foreach (var server in _mServers.Keys) { _mac.AboutToBegin(1); httpclient.GetByteArrayAsync(server).ContinueWith(task => ComputeResult(server, task)); } _mac.AllBegin(AllDone,timeout); Console.WriteLine(""); } private void ComputeResult(string server, Task<Byte[]> task) { object result; if (task.Exception != null) { result = task.Exception.InnerException; } else { //线程池处理IO result = task.Result.Length; } //保存返回结果的长度 _mServers[server] = result; _mac.JustEnded(); } public void Cancel() { _mac.Cancel(); } private void AllDone(CoordinationStatus status) { sp.Stop(); Console.WriteLine("响应耗时总计{0}",sp.Elapsed); switch (status) { case CoordinationStatus.Cancel: Console.WriteLine("操作取消"); break; case CoordinationStatus.AllDone: Console.WriteLine("操作完成,完成的结果如下"); foreach (var server in _mServers) { Console.WriteLine("{0}",server.Key); object result = server.Value; if (result is Exception) { Console.WriteLine("错误原因{0}",result.GetType().Name); } else { Console.WriteLine("返回字节数为:{0}",result); } } break; case CoordinationStatus.Timeout: Console.WriteLine("操作超时"); break; default: throw new ArgumentOutOfRangeException("status", status, null); } } }Il est fortement recommandé de se référer au code ci-dessus. Je ferai souvent référence à ce modèle lors de l'accès au serveur.
Verrouillage rotatif simple
class SomeResource { private SimpleSpinLock s1 = new SimpleSpinLock(); public void AccessResource() { s1.Enter(); //一次是有一个线程才能进入访问 s1.Leave(); } } class SimpleSpinLock { private int _mResourceInUse; public void Enter() { while (true) { if(Interlocked.Exchange(ref _mResourceInUse,1)==0) return; } } public void Leave() { Volatile.Write(ref _mResourceInUse,1); } }Il s'agit d'une implémentation simple d'un verrou de synchronisation de thread, le plus grand Le problème avec les verrous est que lorsqu'il y a concurrence, le thread "tourne", ce qui fera perdre un temps précieux au processeur et empêchera le processeur d'effectuer plus de travail. Par conséquent, ce type de verrou tournant doit être utilisé pour protéger ceux-ci. exécution de code très rapide.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!