まず最初に、確かなことが 1 つあります。Microsoft のフレームワーク クラス ライブラリ (FCL) は、すべての静的メソッドがスレッド セーフであることを保証します。
FCL は、インスタンス メソッドがスレッドセーフであることを保証しません。すべてのロックを追加すると、パフォーマンスが大幅に低下するためです。さらに、各インスタンス メソッドでロックの取得と解放が必要な場合、実際には、アプリケーションでは常に 1 つのスレッドのみが実行されることになり、パフォーマンスに明らかな影響を及ぼします。
以下では、プリミティブなスレッド同期構造を紹介します。
プリミティブ: コードで使用できる最も単純な構造を指します。ユーザーモードとカーネルモードという 2 つの基本的な構造があります。
ユーザーモード
は、特別なCPU命令を使用してスレッドを調整します。
テクノロジー: volatile キーワード、インターロック クラス (インターロック)、スピンロック (スピン ロック)
共通ロック①: volatile キーワードは、同時に実行される複数のスレッドによってフィールドが変更できることを示します。 volatile として宣言されたフィールドは、コンパイラの最適化の対象になりません (単一スレッドによるアクセスを想定)。 これにより、フィールドには常に最新の値が表示されます。
Interlocked クラス: 複数のスレッドで共有される変数のアトミック操作を提供します。 。いわゆるアトミック操作とは、スレッド スケジューリング メカニズムによって中断されない操作を指します。この操作は、一度開始されると、途中でコンテキストを切り替える (別のスレッドに切り替える) ことなく、最後まで実行されます。
共通ロック②: SpinLock 構造は、ロックの取得を待機している間にスピンする低レベルのミューテックス同期プリミティブです。マルチコア コンピューターでは、待機時間が短く、競合状態がまれであることが予想される場合、SpinLock は他のロック タイプよりも優れたパフォーマンスを発揮します。 SpinLock がロックを取得しなくても、スレッドのタイム スライスが生成されます。 これは、スレッドの優先順位の逆転を回避し、ガベージ コレクターが実行を継続できるようにするために行われます。 SpinLock を使用する場合は、非常に短い時間以上ロックを保持しているスレッドがないこと、およびロックを保持している間にスレッドがブロックされていないことを確認してください。
利点:
可能な限りプリミティブ ユーザー モード コンストラクトを使用する必要があります。これらはカーネル モード コンストラクトよりも大幅に高速です。
スレッドの調整はハードウェアで行われます (これが非常に高速な理由です)。
しかし、Microsoft Windows オペレーティング システムは、プリミティブ ユーザー モード構造でスレッドがブロックされていることを決して検出しません。
ユーザーモードのプリミティブ構造でブロックされているスレッド プールはブロックされているとはみなされないため、スレッド プールはそのような一時スレッドを置き換える新しいスレッドを作成しません。
これらの CPU 命令は、比較的短期間のみスレッドをブロックします。
欠点:
スレッドの実行を停止できるのは Windows オペレーティング システムのカーネルだけです (CPU 時間の無駄を防ぎます)。
ユーザー モードで実行されているスレッドはシステムによってプリエンプトされる可能性がありますが、スレッドはできるだけ早く再度スケジュールされます。
リソースを取得したいが一時的に取得できないスレッドは、ユーザー モードで「スピン」し続けるため、大量の CPU 時間を浪費する可能性があります。スレッドは常に 1 つの CPU 上で実行されており、これを「ライブロック」と呼びます。
例: Windows オペレーティング システム自体によって提供される
using System;using System.Threading;public class Worker { // This method is called when the thread is started. public void DoWork() { while (!_shouldStop) { Console.WriteLine("Worker thread: working..."); } Console.WriteLine("Worker thread: terminating gracefully."); } public void RequestStop() { _shouldStop = true; } // Keyword volatile is used as a hint to the compiler that this data // member is accessed by multiple threads. private volatile bool _shouldStop; }public class WorkerThreadExample { static void Main() { // Create the worker thread object. This does not start the thread. Worker workerObject = new Worker(); Thread workerThread = new Thread(workerObject.DoWork); // Start the worker thread. workerThread.Start(); Console.WriteLine("Main thread: starting worker thread..."); // Loop until the worker thread activates. while (!workerThread.IsAlive) ; // Put the main thread to sleep for 1 millisecond to // allow the worker thread to do some work. Thread.Sleep(1); // Request that the worker thread stop itself. workerObject.RequestStop(); // Use the Thread.Join method to block the current thread // until the object's thread terminates. workerThread.Join(); Console.WriteLine("Main thread: worker thread has terminated."); } // Sample output: // Main thread: starting worker thread... // Worker thread: working... // Worker thread: working... // Worker thread: working... // Worker thread: working... // Worker thread: working... // Worker thread: working... // Worker thread: terminating gracefully. // Main thread: worker thread has terminated.}
カーネル モード
。アプリケーションのスレッドでオペレーティング システム カーネルによって実装された関数を呼び出す必要があります。
テクノロジー: EventWaitHandle (イベント)、Semaphore (セマフォ)、Mutex (ミューテックス)
System.Object System.MarshalByRefObject System.Threading.WaitHandle System.Threading.EventWaitHandle System.Threading.Mutex System.Threading.Semaphore
共通ロック③: Mutex クラスは、アプリケーション ドメインの境界を越えたマーシャリングに使用できる Win32 構造のラッパーです。異なるプロセスのスレッドを同期するために使用できます。
利点:
スレッドがカーネル モード コンストラクトを通じて別のスレッドが所有するリソースを取得すると、Windows は CPU 時間の無駄を防ぐためにスレッドをブロックします。リソースが使用可能になると、Windows はスレッドを再開し、リソースへのアクセスを許可します。 CPU の「スピン」を占有しません。
ネイティブ スレッドとマネージド スレッドを相互に同期できるようにします。
同じマシン上の異なるプロセスで実行されているスレッドを同期できます。
セキュリティ設定を適用して、不正なアカウントによるアクセスを防ぐことができます。
スレッドは、連携しているすべてのカーネル モード コンストラクトが利用可能になるまで、またはセット内のいずれかのカーネル モード コンストラクトが利用可能になるまでブロックできます。
カーネルモード構造でブロックされたスレッドはタイムアウト値を指定できます。指定された時間内に目的のリソースにアクセスできない場合、スレッドのブロックを解除して他のタスクを実行できます。
短所:
スレッドをユーザー モードからカーネル モード (またはその逆) に切り替えると、パフォーマンスに大きな影響が生じます。これが、まさにカーネル コンストラクトが避けられる理由です。また、スレッドが常にブロックされていると、「デッドロック」(デッドロック) が発生します。
デッドロックは常にライブロックが原因で発生します。ライブロックは CPU 時間を浪費し、メモリ (スレッド スタックなど) を浪費しますが、デッドロックはメモリを浪費するだけだからです。
ハイブリッド構造
兼具上面两者的长处。在没有竞争的情况下,快而且不会阻塞(就像用户模式)。在有竞争的情况,希望它被操作系统内核阻塞。
技术:ManualResetEventSlim类、SemaphoreSlim类、Monitor类、Lock类、ReaderWriterLockSlim类、CountdownEvent类、Barrier类、双检锁.
常见锁④:Monitor 通常更为可取,因为监视器是专门为 .NET Framework 而设计的,因而它比Mutex可以更好地利用资源。尽管 mutex 比监视器更为强大,但是相对于 Monitor 类,它所需要的互操作转换更消耗计算资源。
常见锁⑤:使用 lock (C#) 或 SyncLock (Visual Basic) 关键字是Monitor的封装。通常比直接使用 Monitor 类更可取,一方面是因为 lock 或 SyncLock 更简洁,另一方面是因为lock 或 SyncLock 确保了即使受保护的代码引发异常,也可以释放基础监视器。
常见锁⑥:ReaderWriterLock 锁,在某些情况下,可能希望只在写入数据时锁定资源,在不更新数据时允许多个客户端同时读取数据。ReaderWriterLock 类在线程修改资源时将强制其独占访问资源,但在读取资源时则允许非独占访问。 ReaderWriter 锁可用于代替排它锁。使用排它锁时,即使其他线程不需要更新数据,也会让这些线程等待。
双检锁
常见锁⑦:双重检查锁定模式(也被称为”双重检查加锁优化”,”锁暗示”(Lock hint)) 是一种软件设计模式用来减少并发系统中竞争和同步的开销。
双重检查锁定模式首先验证锁定条件(第一次检查),只有通过锁定条件验证才真正的进行加锁逻辑并再次验证条件(第二次检查)。
它通常用于减少加锁开销,尤其是为多线程环境中的单例模式实现“惰性初始化”。惰性初始化的意思是直到第一次访问时才初始化它的值。
public sealed class Singleton { private static volatile Singleton instance = null; private static object syncRoot = new Object(); private static int count = 100; private Singleton() { } public static Singleton Instance { get { if (instance == null) { lock (syncRoot) { if (instance == null) instance = new Singleton(); } } return instance; } } public static Singleton GetSingleton() { if (instance == null) { lock (syncRoot) { if (instance == null) { instance = new Singleton(); } } } return instance; } public override string ToString() { lock (syncRoot) { int buf = --count; Thread.Sleep(20); return buf + "_" + count.ToString(); } } }static void Main(string[] args) { Task<string>[] tasks = new Task<string>[10]; Stopwatch watch = new Stopwatch(); object syncRoot = new Object(); watch.Start(); for (int i = 0; i < tasks.Length; i++) { tasks[i] = Task.Factory.StartNew<string>(() =>(Singleton.GetSingleton().ToString())); } Task.WaitAll(tasks, 5000);//设置超时5s watch.Stop(); Console.WriteLine("Tasks running need " + watch.ElapsedMilliseconds + " ms." + "\n"); for (int i = 0; i < tasks.Length; i++) { //超时处理 if (tasks[i].Status != TaskStatus.RanToCompletion) { Console.WriteLine("Task {0} Error!", i + 1); } else { //save result Console.WriteLine("Tick ID is " + tasks[i].Result); Console.WriteLine(); } } for (int i = 0; i < 3; i++) { Console.WriteLine("Main thread do work!"); Thread.Sleep(200); } Console.ReadKey(); }
输出:
Tasks running need 298 ms. Tick ID is 96_96 Tick ID is 99_99 Tick ID is 97_97 Tick ID is 98_98 Tick ID is 95_95 Tick ID is 94_94 Tick ID is 93_93 Tick ID is 92_92 Tick ID is 91_91 Tick ID is 90_90 Main thread do work! Main thread do work! Main thread do work!
以上就是从0自学C#12--线程同步解决方法汇总以及优缺点的内容,更多相关内容请关注PHP中文网(www.php.cn)!