Heim  >  Artikel  >  Backend-Entwicklung  >  Detaillierte Einführung in den Thread-Synchronisationscode in C#

Detaillierte Einführung in den Thread-Synchronisationscode in C#

黄舟
黄舟Original
2017-03-06 11:25:091414Durchsuche

In diesem Artikel werden hauptsächlich die relevanten Kenntnisse der Thread-Synchronisation in C# vorgestellt. Es hat einen sehr guten Referenzwert, schauen wir es uns mit dem Editor unten an

Vorwort

Wenn ein Thread im Thread-Pool blockiert ist, wird der Thread-Pool blockiert wird zusätzliche Threads erstellen, und das Erstellen, Zerstören und Planen von Threads erfordert ziemlich teure Speicherressourcen. Darüber hinaus sind viele Entwickler es gewohnt, mehr Threads zu erstellen, wenn sie feststellen, dass die Threads ihrer Programme nichts Nützliches tun, um skalierbare und zu erstellen Responsive Programme , wir haben die detaillierte Erklärung der asynchronen C#-Programmierung bereits vorgestellt

Allerdings gibt es auch ernsthafte Probleme bei der asynchronen Programmierung. Wenn zwei verschiedene Threads auf dieselben Variablen und Daten zugreifen, entsprechend der Implementierung unserer asynchronen Funktion. Nein Möglicherweise greifen zwei Threads gleichzeitig auf dieselben Daten zu. In diesem Fall benötigen wir eine Thread-Synchronisierung. Wenn mehrere Threads gleichzeitig auf gemeinsam genutzte Daten zugreifen, kann die Thread-Synchronisierung eine Datenbeschädigung verhindern. Der Grund, warum das Konzept der Gleichzeitigkeit betont wird, liegt darin, dass das Wesen der Thread-Synchronisierung ein Zeitproblem ist.

Asynchron und synchron sind relativ. Nach der Ausführung wird die nächste ausgeführt, was Warten und koordinierte Vorgänge erfordert. Asynchron bedeutet, dass sie unabhängig voneinander sind und weiterhin ihre eigenen Aufgaben erledigen, während sie auf ein Ereignis warten. Es besteht keine Notwendigkeit, auf den Abschluss des Ereignisses zu warten, bevor sie wieder arbeiten. Threads sind eine Möglichkeit, Asynchronität zu erreichen. Asynchron bedeutet, dass der Hauptthread, der die Methode aufruft, nicht synchron auf den Abschluss eines anderen Threads warten muss, sodass der Hauptthread andere Dinge tun kann.

Primitive Benutzermodus- und Kernelmodus-Konstrukte

Grundlegende Konzepte

Primitive: Ja Einfache Konstrukte im Code verwendet

Benutzermodus: Durch die Koordinierung von Threads durch spezielle CPU-Anweisungen erkennt das Betriebssystem niemals eine Threadblockierung auf einem primitiven Benutzermoduskonstrukt.

Kernel-Modus: Von Windows selbst bereitgestellt, werden vom Kernel implementierte Funktionen im Thread der Anwendung aufgerufen.

Benutzermodus-Konstrukte

Volatile Konstrukte

C#-Compiler, JIT-Compiler und CPUs werden Optimieren Sie den Code und versuchen Sie sicherzustellen, dass unsere Absichten beibehalten werden. Aus Multithreading-Sicht können unsere Absichten jedoch nicht beibehalten werden:

 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);
 }

Wenn der Compiler prüft, dass Stop falsch ist, generiert er Code zum Eintritt in eine Endlosschleife und erhöht x in der Schleife weiter, also Die Optimierung Die Schleife wird schnell abgeschlossen, aber der Compiler erkennt das Stoppen nur einmal, nicht jedes Mal.

Beispiel 2 --- Zwei Threads greifen gleichzeitig zu:

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();
 }
 }

Wenn das Programm ausgeführt wird, muss der Compiler die Variable m_flag ändern und m_value werden aus dem RAM in das CPU-Register gelesen. RAM übergibt zunächst den Wert von m_value 0, und Thread1 ändert den Wert auf 5, aber Thread2 weiß nicht, dass Thread2 immer noch denkt, dass der Wert 0 ist. Im Allgemeinen ist dieses Problem wahrscheinlicher Bei Mehrkern-CPUs gilt: Je mehr CPUs vorhanden sind, desto größer ist die Wahrscheinlichkeit, dass mehrere Threads gleichzeitig auf Ressourcen zugreifen.

Das Schlüsselwort volatile deaktiviert einige Optimierungen, die vom C#-Compiler, JTP-Compiler und der CPU durchgeführt werden. Wenn es auf Variablen angewendet wird, darf das Feld nicht im CPU-Register zwischengespeichert werden, wodurch sichergestellt wird, dass das Lesen und Schreiben der Feld sind sicher. Tun Sie es im RAM.

Interlocking-Konstrukt

Jede Methode in der System.Threading.Interlocked-Klasse führt einen atomaren Lese- und Schreibvorgang aus, bevor eine Interlocked-Methode aufgerufen wird. Vor diesem Aufruf werden alle Variablenschreibvorgänge ausgeführt an die Interlocked-Methode und alle nach dem Aufruf gelesenen Variablen werden nach diesem Aufruf ausgeführt.

Die Interlocked-Methode führt hauptsächlich statische Operationen für INT32-Variablen wie Add, Decrement, Compare, Exchange, CompareChange und andere Methoden aus und akzeptiert auch Parameter von Objekt-, Double- und anderen Typen.

Atomarer Vorgang: Bezieht sich auf einen Vorgang, der nicht durch den Thread-Planungsmechanismus unterbrochen wird. Sobald dieser Vorgang gestartet ist, wird er bis zum Ende ohne Kontextwechsel (Wechsel zu einem anderen Thread) in der Mitte ausgeführt.

Codedemonstration:

Beschreibung: Fragen Sie mehrere Webserver asynchron über die Interlocked-Methode ab und geben Sie gleichzeitig Daten zurück. Die Ergebnisse werden nur einmal ausgeführt.

//上报状态类型
 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);
 }
 }
 }

Es wird dringend empfohlen, dass Sie sich beim Zugriff auf den Server auf den obigen Code beziehen.

Einfache Spin-Sperre

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);
 }
 }

Dies ist eine einfache Implementierung einer Thread-Synchronisationssperre, die größte Das Problem mit Sperren besteht darin, dass der Thread bei Konkurrenz „spinnt“, was wertvolle Zeit der CPU verschwendet und die CPU daran hindert, mehr Arbeit zu leisten. Daher sollte diese Art von Spin-Sperre verwendet werden, um diese zu schützen Sehr schneller Code wird ausgeführt.

Das Obige ist die detaillierte Einführung des Thread-Synchronisationscodes in C#. Weitere verwandte Inhalte finden Sie auf der chinesischen PHP-Website (www.php.cn)!


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn