あなたは、従来の技術を使用して、複数のスレッドが共有リソースを学びます。
ミューテックス
SemaphoreSlim
AutoResetEvent
manualResetSlim
CountdownEvent
バリア
ReaderWriterLockSlim
スピン待ち- 単語
Mutex 互斥
semaphoreSlim 信号灯限时。
autoResetEvent 自动重置事件
manualResetSlim 重置Slim手动
countDownEvent 倒计时事件
Barrier 障碍物
ReaderWriterLockSlim
SpinWait 旋转等待。
- 読む:2.5,2.6,2.8,2.10を。補足する2.9
2.1はじめに
- スレッドが増減操作を実行すると、他のスレッドが待ちに有効にする必要があり、この共通の問題が参照されるスレッドの同期
- あなたが共有オブジェクトが必要な場合は、スレッドの同期を実行しません。通じほとんどの時間
重新设计程序来移除共享状态,从而去掉复杂的同步构造
。あなたが共有状態を使用する必要がある場合、それはあります使用原子操作
。つまり、現在の操作が完了した後にのみ、他のスレッドは、他の操作を行うことができます。だから、あなたはまた、デッドロック状態を除外し、ロックの使用を避け、現在の操作の完了を待っている他のスレッドを実現していません。 - 前のモードが不可能な場合は、我々はスレッドを調整するさまざまな方法を使用する必要があります。一つの方法は、ブロックされた状態のスレッドを待つことです。この場合は、CPU時間の最小値がかかります。しかし、1は、少なくともコンテキストスイッチ(コンテキストスイッチ、スレッドスケジューラ)を導入することができる、それはかなりのリソースを消費します。スレッドが長時間中断される場合、これはそれだけの価値があります。このように呼ばれて、「カーネル・モード」(MODE-カーネル)のCPU時間の使用を防止するために、唯一のオペレーティングシステムのカーネルスレッドので、。
- ケースのスレッドでわずかな短い時間を待たなければならない、それが遮断状態にスレッドを切り替えることが最善ではありません。CPU時間のような無駄ながら、しかし、コンテキストの切り替えを消費するCPU時間を節約するために。このように呼ばれて、「ユーザーモード」(ユーザーモード)(このアプローチは非常に速く、非常に軽量ですが、スレッドがCPU時間を無駄に長い時間を待たなければならない場合。)
- これらの2つの方法の将来のメイク良いの使用は、あなたが使用することができ、混合モード(ハイブリッド)。待ちに混合モード・ユーザー・モードを使用する最初の試みは、スレッドの待機十分な長さならば、それは、CPUリソースを節約するために、ブロッキング状態に切り替わります。
基本2.2アトミック動作を行います
- アトミックは、オブジェクトの基本的な操作を行うため、競合状態を避けるためにスレッドをブロックしません。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ConsoleApplication3._2
{
public class Class2_2
{
internal abstract class CounterBase
{
public abstract void Increment();
public abstract void Decrement();
}
internal static void TestCounter(CounterBase c)
{
for (int i = 0; i < 10000; i++)
{
c.Increment();
c.Decrement();
}
}
internal class Counter : CounterBase
{
private int _count;
public int Count { get { return _count; } }
public override void Decrement()
{
_count--;
}
public override void Increment()
{
_count++;
}
}
internal class CounterNoLock : CounterBase
{
private int _count;
public int Count { get { return _count; } }
public override void Decrement()
{
Interlocked.Decrement(ref _count);
}
public override void Increment()
{
Interlocked.Increment(ref _count);
}
}
}
}
static void Main(string[] args)
{
#region 2.2
Console.WriteLine("Incorret counter");
var c = new Class2_2.Counter();
var t1 = new Thread(() => Class2_2.TestCounter(c));
var t2 = new Thread(() => Class2_2.TestCounter(c));
var t3 = new Thread(()=>Class2_2.TestCounter(c));
t1.Start();
t2.Start();
t3.Start();
t1.Join();
t2.Join();
t3.Join();
Console.WriteLine("total count:{0}",c.Count);
Console.WriteLine("----------");
Console.WriteLine("correct counter");
var c1 = new Class2_2.CounterNoLock();
t1 = new Thread(()=>Class2_2.TestCounter(c1));
t2 = new Thread(()=>Class2_2.TestCounter(c1));
t3 = new Thread(()=>Class2_2.TestCounter(c1));
t1.Start();
t2.Start();
t3.Start();
t1.Join();
t2.Join();
t3.Join();
Console.WriteLine("total count:{0}", c1.Count);
Console.ReadKey( );
#endregion
}
- Mainメソッドの中にコード読み取りコード
c.Count
の結果はランダムであり、かつc1.Count
によってInterlocked
クラス、任意のオブジェクトは、正しい結果を得ることができますロックされません。インターロックは、アトミック基本的な数学演算インクリメント、デクリメントする方法を提供するなどの追加します。
2.3クラスのMutex
- ミューテックス(相互排他)が唯一の彼らにスレッド上の共有リソースへの排他的アクセスを許可する原始的な方法の同期化、です。
//Main方法
#region 2.3
const string MutexName = "abcd";
using (var m = new Mutex(false, MutexName))
{
if(!m.WaitOne(5000,false))
{
Console.WriteLine("second instance is running");
Console.ReadLine();
m.ReleaseMutex();
}
else
{
Console.WriteLine("running");
Console.ReadLine();
m.ReleaseMutex();
}
}
#endregion
- ミューテックスコンストラクタはミューテックスが作成されている場合、プログラムは、ミューテックスを取得することを可能にすることを示す、偽渡します。あなたがmutexを取得しない場合、プログラムは押しに任意のキーを待って、シンプルなランニングを表示し、その後、ミューテックス、終了をリリース。
- 私たちは、コンソールウィンドウをコンパイルした最初の実行が実行して表示され、どれも二回目を開けないようにした後、第二のウィンドウは5秒以内にミューテックスを取得しようとします。最初のプログラムの後に入力された内容は、「実行中」、プログラムの意志出力二閉じている場合。最初のウィンドウがタイムアウトになりません場合は、2番目のウィンドウには、「......秒」ミューテックス、および出力を得ることができません。
好ましくはmutexオブジェクトをラップするコードブロックを使用することにより、適切に近いミューテックスを確認してください。モードは、同時スレッドが使用シナリオの多数に拡張することができる異なるプログラムで使用することができます。
2.4 SemaphoreSlimクラス(セマフォ)
- このクラスは、スレッドの数が同時に同じリソースにアクセス制限されます。セマフォは、クラスの軽量バージョンです。
static SemaphoreSlim _se = new SemaphoreSlim(4);
static void AccessDataBase(string name,int s)
{
Console.WriteLine("{0} 等待访问数据库 ",name);
_se.Wait();
Console.WriteLine("{0} 被允许访问数据库",name);
Thread.Sleep(s*1000);
Console.WriteLine("{0} 结束掉了 ",name);
_se.Release();
}
static void Main(string[] args)
{
for (int i = 0; i <=6; i++)
{
string threadName = "thread" + i;
int s = 2 + 2 * i;
var t = new Thread(() => AccessDataBase(threadName, s));
t.Start();
}
}
ワークス
- 場合は、メインプログラムが起動し、コンストラクタの中で許可される同時スレッド数を指定して、インスタンスSemaphoreSlimを作成します。主な方法は、各データベースにアクセスしようとすると、6つの異なるスレッドを開始します。しかし、セマフォシステムは、4つの同時スレッドの量を制限します。データベースへの4件の獲得アクセスがある場合は、スレッドが信号を送信するためにリリースメソッドを呼び出すまで、他の二つの必要性は、作業が完了するまで待機します。
- ここでは、私たちが待っている時間が非常に短いケースなしでコンテキストスイッチを使用することができます混在モードを使用します。その後、
semaphore
クラスの用途は、カーネルモード。一般的ではない、それを使用する必要がありますが、アプリケーション間で同期されたシーンでそれを使用することができます。
2.5を使用するクラスAutoRestEvent
- このクラスを実装:別のスレッドへのスレッドから政党へのセンドの通知。これは、いくつかのイベントが発生したため、スレッドが待っているに通知することができます。
#region 2.5
private static AutoResetEvent _w = new AutoResetEvent(false);
private static AutoResetEvent _m = new AutoResetEvent(false);
static void Process(int s)
{
Console.WriteLine("开始一个工作...");
Thread.Sleep(s*1000);
Console.WriteLine("正在工作");
_w.Set();
Console.WriteLine("等待主线程完成它的工作");
_m.WaitOne();
Console.WriteLine("开始第二个操作...");
Thread.Sleep(s*1000);
Console.WriteLine("工作完成");
_w.Set();
}
#endregion
static void Main(string[] args)
{
#region 2.5
var t = new Thread(()=>Process(10));
t.Start();
Console.WriteLine("等待其他线程完成");
_w.WaitOne();
Console.WriteLine("第一个操作完成");
Console.WriteLine("在主线程上执行操作");
_w.WaitOne();
Console.WriteLine("第二个线程完成!");
#endregion
}
- メインプログラムが起動し、2つの設定AutoRestEventインスタンス。チェンFaxin番号に本線から子スレッドは、別の例では、メインラインチェンXiangzaiスレッドからの信号です。私たちは、autorestEventコンストラクタに虚偽のは、これら2つの例の初期状態は、符号なしで定義されて渡します。あなたが設定メソッドを呼び出すまで、任意のスレッドは、これら2つのオブジェクトのいずれかのwaiOneメソッドを呼び出すことを、この手段がブロックされます。最初のイベントのステータスがtrueの場合は呼び出し元のスレッドWAITONE方法がすぐに処理される場合、autoRestEventインスタンスの状態は、(信号)を知らされます。この方法は、設定例一度呼び出される必要があるので、自動的にこのインスタンス・コールWAITONE方法における他のスレッドがこのように継続するように、イベントのステータスが未署名の変更
その後、我々が第二のスレッドを持って、そのコロンビアの操作10sが行い、次いで第2のスレッドからの信号を待ちます。信号は、最初の操作が完了したことを意味します。今、2番目のスレッドがメインスレッドの待ち時間を知らせます。私たちは、メインスレッド上のいくつかの追加の作業をした、と_m.Setメソッドを呼び出すことにより、信号を送ります。次いで第2のスレッドから別の信号を待ちます。AutoRestEventクラスは、待機時間が長すぎるではない、時間のカーネルモードになります。
2.6クラスManualRestEventSlim
- このクラスは、スレッド間のより柔軟な方法で信号を送信します。
#region 2.6
static ManualResetEventSlim _m = new ManualResetEventSlim(false);
static void TravelThroughGates(string threadName,int seconds)
{
Console.WriteLine("{0} 睡着了", threadName);
Thread.Sleep(seconds * 1000);
Console.WriteLine("{0} 等待开门!",threadName);
_m.Wait();
Console.WriteLine("{0} 进入大门!",threadName);
}
#endregion
static void Main(string[] args)
{
#region 1.
#endregion
#region 2.6
var t1 = new Thread(()=> TravelThroughGates("thread1",5));
var t2 = new Thread(()=> TravelThroughGates("thread2",6));
var t3 = new Thread(() => TravelThroughGates("thread3",12));
t1.Start();
t2.Start();
t3.Start();
Thread.Sleep(3000);
Console.WriteLine("门打开了!");
_m.Set();
Thread.Sleep(2000);
_m.Reset();
Console.WriteLine("门关闭了1!");
Thread.Sleep(10000);
Console.WriteLine("门第二次打开了!");
_m.Set();
Thread.Sleep(2000);
Console.WriteLine("门关闭了2!");
_m.Reset();
Console.ReadLine( );
#endregion
}
- ManualRestEventSlim全体はドアを通って少し群衆のように動作します。回転ドアのようなAutoResetEventイベントを通じて、一人だけをすることができます。ManualRestEventSlimは、手動リセット方法を調整するまで、混合バージョンManualRestEventは、ドアが開いたままです。呼び出されたときに
_m.Set
、準備ができてスレッドを許可するようにドアを開けるのと同等の信号を受信して、作業を続けます。それでも、その後の睡眠、追いつくために時間がないし3を通します。コールは、とき_m.Reset()
にドアを閉じることと等価です。最後のスレッドが実行する準備ができているが、数秒間待つように、ある次の信号、待たなければなりませんでした。 - EventWaitHandleクラスは基底クラスとManualRestEvent AutoRestEventクラスです。
2.7 CountDownEventクラス
- これは、業務の一定数が完了するまで待機するコード信号CountDownEventクラスの使用方法を示します。
#region 2.7
static CountdownEvent _c = new CountdownEvent(2);
static void PerformOperation(string message,int s)
{
Thread.Sleep(s*1000);
Console.WriteLine(message);
_c.Signal();//向CountdownEvent 注册信号,同时减少其计数。
}
#endregion
static void Main(string[] args)
{
#region 1.
#endregion
#region 2.7
Console.WriteLine("开始两个操作");
var t1 = new Thread(()=>PerformOperation("操作1完成了",4));
var t2 = new Thread(()=>PerformOperation("操作2完成了",8));
t1.Start();
t2.Start();
_c.Wait();
Console.WriteLine("两个操作完成了!");
_c.Dispose();
Console.ReadKey();
#endregion
}
ワークス
- CountdownEvent例を作成するときにメインプログラムが開始されると、2つの信号は、そのコンストラクタであろう場合に、指定された動作が終了します。そして、実行が完了すると、二つのスレッドを起動すると、信号を送信します。第二のスレッドが完了したら、待っているCountdowneventの状態から、メインスレッドが戻るとは続行します。状況では、複数の非同期操作を待つ必要が完了すると、このアプローチの使用は非常に便利です。しかし、大きな欠点があります。コールは次の場合に
_c.Signal()
倍の特定の番号にはない、そして_c.Wait()
待ちます。すべてのスレッドが完了メソッドの後に信号を呼び出す必要があり、countdownEventを使用してください。
- CountdownEvent例を作成するときにメインプログラムが開始されると、2つの信号は、そのコンストラクタであろう場合に、指定された動作が終了します。そして、実行が完了すると、二つのスレッドを起動すると、信号を送信します。第二のスレッドが完了したら、待っているCountdowneventの状態から、メインスレッドが戻るとは続行します。状況では、複数の非同期操作を待つ必要が完了すると、このアプローチの使用は非常に便利です。しかし、大きな欠点があります。コールは次の場合に
2.8を使用するバリアクラス
- このクラスは、ある時点で複数のスレッドミートを整理するために使用されます。これは、各スレッドは、コールバック関数がSignalAndWait法後に実行される呼び出し、コールバック機能を提供します。
*役割を説明するために、Microsoftライブラリ:複数のステージで動作するようにアルゴリズムに従って、並行して複数のタスクを採用することができるようにします。
c #region 2.8 static Barrier _b = new Barrier(2, b => Console.WriteLine("end of phase {0}",b.CurrentPhaseNumber+1));//获取屏障的当前阶段的编号。 static void PlayMusic(string name,string message,int s) { for (int i = 0; i < 3; i++) { Console.WriteLine("---------"); Thread.Sleep(s*1000); Console.WriteLine("{0} starts to {1}",name,message); Thread.Sleep(s*1000); Console.WriteLine("{0} finishes to {1}",name,message); _b.SignalAndWait(); } } #endregion static void Main(string[] args) { var t1 = new Thread(()=>PlayMusic("吉他手","play an amzing solo",5)); var t2 = new Thread(()=> PlayMusic("歌手 ","sing his song",2)); t1.Start(); t2.Start(); Console.Read(); } #endregion
ワークス
- 私たちが望む指定には二つのスレッドを同期することをバリアクラスを作成します。いずれかのコール中の2つのスレッドで
_b.SignalAndWait
方法の後段階を印刷するためにコールバック関数を実行します。
各スレッドは、2つの信号の障壁をお送りしますので、2つのステージが存在します。これら2つのスレッドがsignalAndWaitメソッドを呼び出すたびに、障壁は、コールバック関数を実行します。これは、マルチスレッドの反復で、あなたは各反復の終了前に、いくつかの計算を行うことができ便利です。最後のスレッドがsignalandwaitメソッドを呼び出すときは、イテレーションの終わりに対話することができます。
- 私たちが望む指定には二つのスレッドを同期することをバリアクラスを作成します。いずれかのコール中の2つのスレッドで
スピン待ちクラスを使用して2.10
- どのようにカーネルスレッドを待機させるためにモデルを使用しません。また、彼はハイブリッド同期スピン待ち構造だった、それはいくつかの時間を待つために使用したユーザ・モードに設計され、その後、CPU時間を節約するために、カーネルモードに切り替えます。
*マイクロソフトのライブラリ説明:スピン待ちがスピンベースの待機のためのサポートを提供します。
#region 2.10
static volatile bool _isOver = false;
static void UserModeWait()
{
while (!_isOver)
{
Console.WriteLine(".");
}
Console.WriteLine();
Console.WriteLine("waiting is complete");
}
static void HybridSpinWait()
{
var w = new SpinWait();
while (!_isOver)
{
w.SpinOnce();
Console.WriteLine(w.NextSpinWillYield);
}
Console.WriteLine("waiting is complete");
}
#endregion
static void Main(string[] args)
{
#region 2.10
var t1 = new Thread(UserModeWait);
var t2 = new Thread(HybridSpinWait);
Console.WriteLine("运行用户模式等待");
t1.Start();
Thread.Sleep(20);
_isOver = true;
Thread.Sleep(1000);
_isOver = false;
Console.WriteLine("运行混合自旋等待结构等待");
t2.Start();
Thread.Sleep(5);
_isOver = true;
Console.Read();
#endregion
}
- メインプログラムの開始は、スレッドを定義する際に、無限ループは、リアメインスレッドまでtrueに_isOver変数セット20ミリ秒を実行します。私たちは、タスクマネージャを介してCPUの負荷を測定し、試験期間が20〜30代で実行することができます。CPUコアの数に応じて、タスクマネージャには、かなりの処理時間が表示されます。
_isOverの静的フィールドを宣言するために揮発性のキーワードを使用し、キーワードフィールドは、複数のスレッドが同時に実行されている修正するリターンを示すかもしれません。揮発性フィールドとして宣言は、コンパイラ最適化されず、プロセッサは、単一のスレッドによってアクセスされることができます。これは、フィールドは、最新の値であることを保証します。
次いで、特別なフラグスレッドがブロック状態に切り替えられる各繰り返しに表示される印刷のためのスピンウェイトバージョンを使用します。5ミリ秒の結果を参照してくださいスレッドを実行します。ユーザーモードを使用するには、最初、スピン待ちの試みで、9回の反復の後、スイッチングを開始スレッドがブロックされました。あなたは、CPUの負荷のバージョンを測定しようとすると、勝利は、タスクマネージャのタスクのCPU使用率を表示されません。