.NET 4:スリムシンク

最終的に、 RTMバージョンの .NET 4およびVisual Studio 2010がリリースされ、プラットフォームの最終バージョンでの最終的な最適化が完了し、安全にテストできます。

.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)で起こったことです。

ManualResetEventManualResetEvent Slim
画像画像

違いは明らかであり、非常に重要です-約10倍。

T.O. Set()およびReset()を呼び出すとき、Windowsカーネルのオブジェクトへの長い呼び出しであるとは限らず、かなりの速度で勝つことができるため、ManualResetEventSlimを使用することをお勧めします。

Source: https://habr.com/ru/post/J91027/


All Articles