ホームページ  >  記事  >  バックエンド開発  >  C# のスレッド同期コードの詳細な紹介

C# のスレッド同期コードの詳細な紹介

黄舟
黄舟オリジナル
2017-03-06 11:25:091414ブラウズ

この記事では主にC#におけるスレッド同期の関連知識を紹介します。非常に優れた参考値です。以下のエディターで見てみましょう

前書き

スレッド プール内のスレッドがブロックされると、スレッド プールは追加のスレッドを作成し、スレッドの作成、破棄、スケジュールを実行します。さらに、多くの開発者は、プログラムのスレッドが役に立たないことを認識すると、より多くのスレッドを作成することに慣れています。C# の非同期について詳しく説明しました。先にプログラミングしてください

しかし、2 つの異なるスレッドが同じ変数とデータにアクセスする場合、非同期関数の実装によれば、現時点では 2 つのスレッドが同時に同じデータにアクセスすることは不可能です。スレッドの同期が必要なとき。複数のスレッドが共有データに同時にアクセスする場合、スレッド同期によりデータの破損を防ぐことができます。同時性の概念が重要視されるのは、スレッド同期の本質がタイミングの問題であるためです。

非同期と同期は相対的なものであり、1 つを実行した後、次の実行を待機して調整する必要があります。非同期とは、それらが互いに独立しており、イベントを待機している間も独自の処理を継続することを意味します。再度作業する前にイベントの完了を待つ必要はありません。スレッドは非同期を実現する方法です。非同期とは、メソッドを呼び出すメイン スレッドが別のスレッドの完了を同期的に待つ必要がないため、メイン スレッドは他のことを実行できることを意味します。

プリミティブのユーザーモードとカーネルモードの構成

基本概念

プリミティブ: コードで使用できる単純な構成

ユーザーモード: スレッドは特別な CPU 命令によって調整され、オペレーティングシステムは常に検出されません Aプリミティブなユーザーモード構造でのスレッドのブロック。

カーネル モード: Windows 自体によって提供され、カーネルによって実装された関数はアプリケーションのスレッドで呼び出されます。

ユーザー モード コンストラクト

Volatile コンストラクト

C# コンパイラー、JIT コンパイラー、および CPU はすべて、コードを最適化しようとしますが、マルチスレッドの観点からは、意図は必ずしも保持されるとは限りません。例を次に示します。

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

stop が false であることをコンパイラがチェックすると、無限ループに入るコードが生成され、ループ内で x をインクリメントし続けます。そのため、最適化ループはすぐに完了しますが、コンパイラは停止を毎回ではなく一度だけ検出します。

例 2---2 つのスレッドが同時にアクセスします:

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

プログラムが実行されるとき、コンパイラーは、まず変数 m_flag と m_value を RAM から CPU レジスターに読み取り、m_value 0 の値を RAM に渡す必要があります。 thread1 値を 5 に変更しますが、thread2 は値が 0 であると考えていることを認識しません。一般に、この問題は、CPU の数が多いほど、マルチコア CPU で発生する可能性が高くなります。同時にリソースにアクセスします。

キーワード volatile は、C# コンパイラー、JTP コンパイラー、および CPU によって実行される一部の最適化を無効にし、変数に適用すると、フィールドを CPU のレジスターにキャッシュすることができなくなり、フィールドの読み取りと書き込みが RAM で実行されるようになります。 。

インターロックの構築

System.Threading.Interlocked クラスの各メソッドは、Interlocked メソッドを呼び出す前にアトミックな読み取りおよび書き込み操作を実行し、この Interlocked メソッドの呼び出しよりも先に変数を読み取ります。 after the call は、この呼び出しの後に読み取られます。

Interlocked メソッドは主に、Add、Decrement、Compare、Exchange、CompareChange などのメソッドなどの INT32 変数の静的操作を実行し、オブジェクト、Double などの型のパラメータも受け入れます。

アトミック操作: スレッド スケジューリング メカニズムによって中断されない操作を指します。この操作は、一度開始されると、途中でコンテキストを切り替える (別のスレッドに切り替える) ことなく最後まで実行されます。

コードのデモ:

手順: Interlocked メソッドを通じて複数の Web サーバーに非同期でクエリを実行し、同時にデータを返します。結果は 1 回だけ実行されます。

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

サーバーにアクセスする際には、このモデルを頻繁に参照することを強くお勧めします。

単純なスピン ロック

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

これは、スレッド同期ロックの単純な実装です。この種のロックの最大の問題は、競合があるとスレッドが「スピン」してしまうことです。これにより、貴重な CPU 時間が無駄になり、CPU がそれ以上の作業を実行できなくなります。そのため、このスピン ロックは、非常に高速に実行されるコードを保護するために使用する必要があります。

上記は C# のスレッド同期コードの詳細な紹介です。その他の関連コンテンツについては、PHP 中国語 Web サイト (www.php.cn) に注目してください。


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。