.NET 4の重要な革新の1つがParallel Extensions (コードの並列化を促進し、マルチスレッド環境で動作するためのツールのセット)であることは秘密ではありません。 このセットの他のツールの中には、同期プリミティブもあり、これらも処理されています。
特に、非常に人気の高いプリミティブManualResetEventの修正バージョンが登場しました 。 このツールにあまり慣れていない人のために:このツールを使用すると、異なるスレッドで動作するコードのセクションの実行を同期できます。 オブジェクトには、インストールとアンインストールの2つの状態があります。 一方から他方への遷移は、Set()およびReset()メソッドを使用して実行されます。 簡単に言えば、それがどのように機能するか(ここでmreはManualResetEvent型のインスタンスです):
ストリーム1 | ストリーム2 | 時間 |
---|---|---|
mre.Reset();
mre.WaitOne(); | //コード実行 | 0 |
//待つ | //コード実行 | 1 |
//待つ | //コード実行 | 2 |
//待つ | //コード実行 | 3 |
//待つ | mre.Set(); | 4 |
//コード実行 | // ... | 5 |
.NET 4からのこのプリミティブの改良版はManualResetEventSlimと呼ばれます 。 主なアイデアは、1つのスレッドのみがプリミティブにアクセスする場合のオーバーヘッドを削減することです。 いわゆる 「ハイブリッドスキーム」。次のように実装できます。
internal sealed class SimpleHybridLock : IDisposable
{
private Int32 m_waiters = 0;
private AutoResetEvent m_waiterLock = new AutoResetEvent( false );
public void Enter()
{
if (Interlocked.Increment( ref m_waiters) == 1)
return ;
m_waiterLock.WaitOne();
}
public void Leave()
{
if (Interlocked.Decrement( ref m_waiters) == 0)
return ;
m_waiterLock.Set();
}
public void Dispose()
{
m_waiterLock.Dispose();
}
}
* This source code was highlighted with Source Code Highlighter .
これは、リヒターの著書「C#を介したCLR」第3版の例です。 SimpleHybridLockプリミティブには、パブリックのEnter()およびLeave()メソッドがいくつかあります。 これらのメソッドを呼び出すと、コードのクリティカルセクションがフレーム化され、常に1つのスレッドでのみ実行されます。 クラスコードは非常に透過的です。Enter()を呼び出した最初のスレッドは内部カウンターを1増やします。2番目のスレッドもカウンターを増やし、誰かがm_waiterLockオブジェクトのSet()を呼び出すまでブロックされます。 T.O. プリミティブへの競合アクセスがない場合、パフォーマンスの点で非常に「重い」WaitOne()およびSet()メソッドは呼び出されません。 これは、コードの速度にプラスの影響を与える可能性があります。
ManualResetEventSlimは、同様の原理に基づいて構築されています。 たとえば、再帰呼び出しの制御など、よりインテリジェントなメカニズムがあると思います。 プラットフォームのエンドユーザーとして、ManualResetEventとその* -Slimバージョンの実際のパフォーマンスの違いに興味がありました。 彼女を見つけるために、小さな「ベンチマーク」を用意しました。 これはこの種のコンソールアプリケーションです。
static void Main( string [] args)
{
ManualResetEventSlim mres = new ManualResetEventSlim( false );
ManualResetEventSlim mres2 = new ManualResetEventSlim( false );
ManualResetEvent mre = new ManualResetEvent( false );
long total = 0;
int COUNT = 50;
for ( int i = 0; i < COUNT; i++)
{
mres2.Reset();
//
Stopwatch sw = Stopwatch.StartNew();
//
ThreadPool.QueueUserWorkItem((obj) =>
{
//Method(mres, true);
Method2(mre, true );
mres2.Set();
});
//
//Method(mres, false);
Method2(mre, false );
//,
mres2.Wait();
sw.Stop();
Console .WriteLine( "Pass {0}: {1} ms" , i, sw.ElapsedMilliseconds);
total += sw.ElapsedMilliseconds;
}
Console .WriteLine();
Console .WriteLine( "===============================" );
Console .WriteLine( "Done in average=" + total / ( double )COUNT);
Console .ReadLine();
}
// ManualResetEventSlim
private static void Method(ManualResetEventSlim mre, bool value )
{
//
for ( int i = 0; i < 9000000; i++)
{
if ( value )
{
mre.Set();
}
else
{
mre.Reset();
}
}
}
// ManualResetEvent
private static void Method2(ManualResetEvent mre, bool value )
{
//
for ( int i = 0; i < 9000000; i++)
{
if ( value )
{
mre.Set();
}
else
{
mre.Reset();
}
}
}
}
* This source code was highlighted with Source Code Highlighter .
Main()メソッドでは、プリミティブのインスタンスを作成し、メインスレッドとプールスレッドの2つのスレッドからそれらへのアクセスをモデル化します。 この場合、プールスレッドはループ内の状態を設定し、メインスレッドはリセットされます。 実験をCOUNT回繰り返し、画面に平均値を表示します。 これが私のラップトップ(2コアCPU T7250、Win 7 x64)で起こったことです。
ManualResetEvent | ManualResetEvent Slim |
![]() | ![]() |
違いは明らかであり、非常に重要です-約10倍。
T.O. Set()およびReset()を呼び出すとき、Windowsカーネルのオブジェクトへの長い呼び出しであるとは限らず、かなりの速度で勝つことができるため、ManualResetEventSlimを使用することをお勧めします。