ミューテックスとクリティカルセクションの違いは何ですか?
-
03-07-2019 - |
質問
Linux、Windowsの観点から説明してください。
私はC#でプログラミングしていますが、これら2つの用語が違いを生むでしょうか。例などを含めて、できる限り投稿してください。
ありがとう
解決
Windowsの場合、クリティカルセクションはミューテックスよりも軽量です。
Mutexeはプロセス間で共有できますが、常にオーバーヘッドが発生するカーネルへのシステムコールが発生します。
クリティカルセクションは1つのプロセス内でのみ使用できますが、競合の場合にのみカーネルモードに切り替わるという利点があります-一般的なケースである競合しない取得は非常に高速です。競合の場合、カーネルに入って何らかの同期プリミティブ(イベントやセマフォなど)で待機します。
2つの時間を比較する簡単なサンプルアプリを作成しました。私のシステムでは、1,000,000の競合しない取得と解放のために、mutexが1秒以上かかります。クリティカルセクションは、1,000,000件の取得に約50ミリ秒かかります。
テストコードは次のとおりです。これを実行すると、mutexが1番目または2番目の場合に同様の結果が得られるため、他の効果は見られません。
HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;
// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
}
QueryPerformanceCounter(&end);
int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);
}
QueryPerformanceCounter(&end);
int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
printf("Mutex: %d CritSec: %d\n", totalTime, totalTimeCS);
他のヒント
理論的な観点から、クリティカルセクションは、実行してはならないコードです。コードが共有リソースにアクセスするため、一度に複数のスレッドによって。
A mutex は、使用されるアルゴリズム(および場合によってはデータ構造の名前)です。重要なセクションを保護します。
セマフォおよびモニターは、ミューテックスの一般的な実装です。
実際には、Windowsで利用可能な多くのミューテックス実装があります。実装の結果として、ロックのレベル、スコープ、コスト、および異なるレベルの競合下でのパフォーマンスによって主に異なります。 CLR Inside Outを参照してください- スケーラビリティのための並行性の使用:さまざまなミューテックス実装のコストのグラフ。
利用可能な同期プリミティブ。
lock(object)
ステートメントは、 Monitor
- MSDN 。
ここ数年、非ブロック同期について多くの研究が行われています。目標は、ロックフリーまたは待機フリーの方法でアルゴリズムを実装することです。そのようなアルゴリズムでは、プロセスは他のプロセスが作業を完了するのを助け、プロセスが最終的に作業を完了することができるようにします。結果として、何らかの作業を実行しようとした他のプロセスがハングした場合でも、プロセスは作業を終了できます。ロックを使用すると、ロックは解除されず、他のプロセスが続行できなくなります。
他の回答に加えて、次の詳細はウィンドウの重要なセクションに固有です:
- 競合がない場合、クリティカルセクションの取得は
InterlockedCompareExchange
操作と同じくらい簡単です - クリティカルセクション構造には、ミューテックスの余地があります。最初は未割り当てです
- クリティカルセクションのスレッド間に競合がある場合、ミューテックスが割り当てられて使用されます。クリティカルセクションのパフォーマンスは、ミューテックスのパフォーマンスに低下します
- 高い競合が予想される場合は、スピンカウントを指定してクリティカルセクションを割り当てることができます。
- スピンカウントのあるクリティカルセクションに競合がある場合、クリティカルセクションを取得しようとするスレッドは、その多くのプロセッササイクルの間スピン(ビジー待機)します。別のスレッドへのコンテキスト切り替えを実行するサイクル数は、所有スレッドがミューテックスを解放するために使用するサイクル数よりもはるかに多くなる可能性があるため、これによりスリープよりもパフォーマンスが向上する可能性があります
- スピンカウントが期限切れになると、ミューテックスが割り当てられます
- 所有スレッドがクリティカルセクションを解放するとき、mutexが割り当てられているかどうかを確認する必要があります。割り当てられている場合は、待機スレッドを解放するようmutexを設定します
Linuxでは、「スピンロック」があると思います。スピンカウントのあるクリティカルセクションと同様の目的を果たします。
Critical SectionとMutexはオペレーティングシステム固有ではなく、マルチスレッド/マルチプロセッシングの概念です。
重要セクション いつでも自分自身でのみ実行する必要があるコードです(たとえば、5つのスレッドが同時に実行され、配列を更新する&quot; critical_section_function&quot;という関数があります... 5つのスレッドすべてを更新したくないそのため、プログラムがcritical_section_function()を実行しているとき、他のスレッドはcritical_section_functionを実行する必要はありません。
mutex * Mutexは、クリティカルセクションコードを実装する方法です(トークンのように考えてください... critical_section_codeを実行するには、スレッドがそれを所有している必要があります)
Linuxでの重要な選択に相当する「高速」Windowsは、 futex になります。 、高速ユーザースペースミューテックスの略です。 futexとmutexの違いは、futexではアービトレーションが必要な場合にのみカーネルが関与するため、アトミックカウンターが変更されるたびにカーネルと通信するオーバーヘッドを節約できることです。これにより、一部のアプリケーションでロックをネゴシエートする時間を大幅に節約できます。
mutexを共有するために使用する手段を使用して、プロセス間でfutexを共有することもできます。
残念ながら、futexは実装するのが非常に難しい(PDF)になる可能性があります。 (2018年の更新、2009年ほど怖くはありません)。
それ以外は、両方のプラットフォームでほぼ同じです。共有構造に対してアトミックなトークン駆動型の更新を行っており、(できれば)飢starが発生しないようにします。残っているのは、単にそれを達成する方法です。
ミューテックスは、スレッドが取得できるオブジェクトであり、他のスレッドが取得できないようにします。これは勧告であり、必須ではありません。スレッドは、mutexが表すリソースを取得せずに使用できます。
クリティカルセクションとは、オペレーティングシステムによって中断されないことが保証されているコードの長さです。擬似コードでは、次のようになります。
StartCriticalSection();
DoSomethingImportant();
DoSomeOtherImportantThing();
EndCriticalSection();
Windowsでは、クリティカルセクションはプロセスのローカルにあります。 mutexは、プロセス間で共有/アクセスできます。基本的に、クリティカルセクションははるかに安価です。 Linuxについて具体的にコメントすることはできませんが、一部のシステムでは同じものの単なるエイリアスです。
2セントを追加するために、クリティカルセクションは構造として定義され、それらに対する操作はユーザーモードコンテキストで実行されます。
ntdll!_RTL_CRITICAL_SECTION +0x000 DebugInfo : Ptr32 _RTL_CRITICAL_SECTION_DEBUG +0x004 LockCount : Int4B +0x008 RecursionCount : Int4B +0x00c OwningThread : Ptr32 Void +0x010 LockSemaphore : Ptr32 Void +0x014 SpinCount : Uint4B
mutexは、Windowsオブジェクトディレクトリに作成されたカーネルオブジェクト(ExMutantObjectType)です。ミューテックス操作は、ほとんどカーネルモードで実装されます。たとえば、Mutexを作成する場合、カーネルでnt!NtCreateMutantを呼び出すことになります。
マイケルからのすばらしい回答。 C ++ 11で導入されたmutexクラスの3番目のテストを追加しました。結果はいくぶん興味深いものであり、単一プロセスのCRITICAL_SECTIONオブジェクトに対する彼の元々の支持を引き続きサポートしています。
mutex m;
HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;
// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
}
QueryPerformanceCounter(&end);
int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);
}
QueryPerformanceCounter(&end);
int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
// Force code into memory, so we don't see any effects of paging.
m.lock();
m.unlock();
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
m.lock();
m.unlock();
}
QueryPerformanceCounter(&end);
int totalTimeM = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
printf("C++ Mutex: %d Mutex: %d CritSec: %d\n", totalTimeM, totalTime, totalTimeCS);
私の結果は217、473、および19でした(最後の2つの時間の比率はほぼマイケルのものに匹敵しますが、私のマシンは彼より少なくとも4年若いため、 2009年と2013年、XPS-8700がリリースされたとき)。新しいミューテックスクラスはWindowsミューテックスの2倍の速度ですが、Windows CRITICAL_SECTIONオブジェクトの10分の1未満の速度です。テストしたのは非再帰的ミューテックスのみです。 CRITICAL_SECTIONオブジェクトは再帰的です(同じ回数だけ残っていれば、1つのスレッドが繰り返し入力できます)。