web-dev-qa-db-ja.com

ミューテックスとクリティカルセクションの違いは何ですか?

Linux、Windowsの観点から説明してください。

私はC#でプログラミングしていますが、これら2つの用語が違いを生むでしょうか。例などを含めて、できる限り投稿してください。

ありがとう

128
ultraman

Windowsの場合、クリティカルセクションはミューテックスよりも軽量です。

ミューテックスはプロセス間で共有できますが、常にオーバーヘッドが発生するカーネルへのシステムコールが発生します。

クリティカルセクションは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);
220
Michael

理論的な観点からは、 クリティカルセクション はコードが共有リソースにアクセスするため、複数のスレッドで同時に実行してはならないコードです。

mutex は、クリティカルセクションを保護するために使用されるアルゴリズム(およびデータ構造の名前)です。

セマフォ および モニター は、ミューテックスの一般的な実装です。

実際には、Windowsで利用可能な多くのミューテックス実装があります。実装の結果として、ロックのレベル、スコープ、コスト、および異なるレベルの競合下でのパフォーマンスによって主に異なります。さまざまなmutex実装のコストのチャートについては、 CLR Inside Out-並行性の使用によるスケーラビリティ を参照してください。

利用可能な同期プリミティブ。

lock(object)ステートメントは、 Monitor を使用して実装されます。参照用に [〜#〜] msdn [〜#〜] を参照してください。

ここ数年、多くの研究が 非ブロッキング同期 で行われました。目標は、ロックフリーまたは待機フリーの方法でアルゴリズムを実装することです。そのようなアルゴリズムでは、プロセスは他のプロセスが作業を完了するのを助け、プロセスが最終的に作業を終了できるようにします。結果として、何らかの作業を実行しようとした他のプロセスがハングした場合でも、プロセスは作業を終了できます。ロックを使用すると、ロックは解除されず、他のプロセスが続行できなくなります。

84

他の回答に加えて、次の詳細はウィンドウの重要なセクションに固有のものです。

  • 競合がない場合、クリティカルセクションの取得はInterlockedCompareExchange操作と同じくらい簡単です。
  • クリティカルセクション構造には、ミューテックスの余地があります。最初は未割り当てです
  • クリティカルセクションのスレッド間で競合がある場合、ミューテックスが割り当てられて使用されます。クリティカルセクションのパフォーマンスは、ミューテックスのパフォーマンスに低下します。
  • 高い競合が予想される場合は、スピンカウントを指定してクリティカルセクションを割り当てることができます。
  • スピンカウントのあるクリティカルセクションで競合がある場合、クリティカルセクションを取得しようとするスレッドは、その多くのプロセッササイクルの間スピン(ビジー待機)します。別のスレッドへのコンテキスト切り替えを実行するサイクル数は、所有スレッドがミューテックスを解放するために使用するサイクル数よりもはるかに多くなる可能性があるため、これにより、スリープよりもパフォーマンスが向上する可能性があります
  • スピンカウントが期限切れになると、ミューテックスが割り当てられます
  • 所有スレッドがクリティカルセクションを解放するとき、ミューテックスが割り当てられているかどうかを確認する必要があります。割り当てられている場合は、待機スレッドを解放するようにミューテックスを設定します

Linuxでは、スピンカウントを持つクリティカルセクションと同様の目的を果たす「スピンロック」があると思います。

21

クリティカルセクションとミューテックスはオペレーティングシステム固有ではなく、マルチスレッド/マルチプロセッシングの概念です。

Critical Sectionいつでも自分でのみ実行する必要のあるコードです(たとえば、5つのスレッドが同時に実行され、配列を更新する「critical_section_function」という関数があります... 5つのスレッドすべてが一度に配列を更新することを望まないため、プログラムがcritical_section_function()を実行しているとき、他のスレッドはcritical_section_functionを実行する必要はありません。

mutex * Mutexは、クリティカルセクションコードを実装する方法です(トークンのように考えてください... critical_section_codeを実行するには、スレッドがそれを所有している必要があります)

18
The Unknown

Linuxでの重要な選択に相当する「高速」Windowsは、 futex であり、これは高速ユーザースペースミューテックスを表します。 futexとmutexの違いは、futexではアービトレーションが必要な場合にのみカーネルが関与するため、アトミックカウンターが変更されるたびにカーネルと通信するオーバーヘッドを節約できることです。それは、いくつかのアプリケーションでロックをネゴシエートする時間を大幅に節約できます

ミューテックスを共有するために使用する手段を使用して、futexをプロセス間で共有することもできます。

残念ながら、futexは 実装するのが非常に難しい (PDF)です。 (2018年の更新、2009年ほど怖くはありません)。

それ以外は、両方のプラットフォームでほぼ同じです。共有構造に対してアトミックなトークン駆動型の更新を行っており、(できれば)飢starを引き起こさないようにします。残っているのは、単にそれを達成する方法です。

14
Tim Post

ミューテックスは、スレッドが取得できるオブジェクトであり、他のスレッドが取得できないようにします。これは勧告であり、必須ではありません。スレッドは、mutexが表すリソースを取得せずに使用できます。

クリティカルセクションは、オペレーティングシステムによって中断されないことが保証されているコードの長さです。擬似コードでは、次のようになります。

StartCriticalSection();
    DoSomethingImportant();
    DoSomeOtherImportantThing();
EndCriticalSection();
13
Zifre

2セントを追加するだけで、クリティカルセクションは構造として定義され、それらに対する操作はユーザーモードのコンテキストで実行されます。

ntdll!_RTL_CRITICAL_SECTION 
 + 0x000 DebugInfo:Ptr32 _RTL_CRITICAL_SECTION_DEBUG 
 + 0x004 LockCount:Int4B 
 + 0x008 RecursionCount:Int4B 
 + 0x00c V + 0x010 LockSemaphore:Ptr32 Void 
 + 0x014 SpinCount:Uint4B 

一方、mutexはWindowsオブジェクトディレクトリに作成されたカーネルオブジェクト(ExMutantObjectType)です。 mutex操作は、ほとんどカーネルモードで実装されます。たとえば、Mutexを作成する場合、カーネルでnt!NtCreateMutantを呼び出すことになります。

6
Martin

Windowsでは、クリティカルセクションはプロセスのローカルにあります。ミューテックスは、プロセス間で共有/アクセスできます。基本的に、クリティカルセクションははるかに安価です。特にLinuxについてコメントすることはできませんが、一部のシステムでは同じもののエイリアスにすぎません。

6
Promit

マイケルからの素晴らしい答え。 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つの時間の比率はMichaelのそれとほぼ同等ですが、私のマシンは彼より少なくとも4年若いため、2009年から2013年の間に速度が向上している証拠を見ることができます、XPS-8700が登場したとき)。新しいミューテックスクラスはWindowsミューテックスの2倍の速度ですが、Windows CRITICAL_SECTIONオブジェクトの10分の1未満の速度です。テストしたのは非再帰的ミューテックスのみです。 CRITICAL_SECTIONオブジェクトは再帰的です(同じ回数だけ出れば、1つのスレッドが繰り返しオブジェクトを入力できます)。

1
Stevens Miller