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


134

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

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

ありがとう

回答:


232

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

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

重要なセクションは1つのプロセス内でのみ使用できますが、競合が発生した場合にのみカーネルモードに切り替わるという利点があります。一般的なケースである非競合取得は非常に高速です。競合の場合、それらはカーネルに入り、同期プリミティブ(イベントやセマフォなど)を待機します。

2つの時間を比較する簡単なサンプルアプリを作成しました。1,000,000の競合しない取得と解放のシステムでは、ミューテックスに1秒かかります。クリティカルセクションは、1,000,000回の集録に約50 msかかります。

これがテストコードです。これを実行し、ミューテックスが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);

1
これが関係しているかどうかはわかりませんが(コードをコンパイルして試していなかったため)、INFINITEを指定してWaitForSingleObjectを呼び出すと、パフォーマンスが低下することがわかりました。1のタイムアウト値を渡してから、戻り値を確認しながらループすると、一部のコードのパフォーマンスに大きな違いが生じました。これは、ほとんどの場合、外部プロセスハンドルを待機している状況で発生します。YMMV。その変更でmutexがどのように機能するかを確認したいと思います。このテストからの結果の時間差は、予想されるよりも大きいようです。
トロイハワード

5
@TroyHoward基本的にその時点でスピンロックではないのですか?
dss539 2013

この区別の理由は、おそらく主に歴史的なものです。競合しないケース(アトミックな命令がほとんどなく、syscallがない)でCriticalSectionと同じ速さでありながら、(共有メモリの一部を使用して)プロセス間で機能するロックを実装することは難しくありません。Linux futexesなどを参照してください。
regnarg 2015年

2
@TroyHowardは、CPUを常に100%で実行して、INFINITEが適切に機能するかどうかを確認してください。私のマシン(Dell XPS-8700)では、電源ストラテジーがスローダウンすることを決定した後、フルスピードまでクロールするのに40ミリ秒かかることがあります。
Stevens Miller

ここで何が示されているのか理解できません。一般に、クリティカルセクションに入るには、ある種のセマフォを取得する必要があります。裏側では、O / Sにはミューテックスを必要とせずにこのクリティカルセクションの動作を実装する効率的な方法があると言っていますか?
SN

89

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

ミューテックスは、クリティカルセクションを保護するために使用されている(時々 、データ構造の名前)アルゴリズムです。

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

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

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

lock(object)文は使用して実装されているMonitor参照- MSDNを参照するために。

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


受け入れられた答えを見て、私はあなたが書いた理論的見解を見るまで、重要なセクションの概念が間違っていることを覚えているのではないかと思っていました。:)
Anirudh Ramanathan 2012

2
実用的なロックフリープログラミングは、存在することを除いて、シャングリラに似ています。Keir Fraserの論文(PDF)は、これをかなり興味深い方法で調査しています(2004年に遡ります)。そして、私たちは2012年もまだそれに苦労しています。
Tim Post

22

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

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

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


残念ながら、ウィンドウのクリティカルセクションには、カーネルモードでのCAS操作の実行が含まれます。これは、実際の連動操作よりも非常にコストがかかります。また、Windowsのクリティカルセクションには、スピンカウントを関連付けることができます。
2009

2
それは間違いなく真実ではありません。CASは、ユーザーモードでcmpxchgを使用して実行できます。
マイケル

InitializeCriticalSectionを呼び出した場合、デフォルトのスピンカウントはゼロだと思いました。スピンカウントを適用したい場合は、InitializeCriticalSectionAndSpinCountを呼び出す必要があります。そのためのリファレンスはありますか?
1800情報

18

Critical SectionとMutexはオペレーティングシステム固有ではなく、マルチスレッド/マルチプロセッシングの概念です。

クリティカルセクション は、常に自分でのみ実行する必要があるコードの一部です(たとえば、同時に実行される5つのスレッドと、配列を更新する「critical_section_function」と呼ばれる関数があります... 5つのスレッドすべてが必要ではありません。配列を一度に更新するため、プログラムがcritical_section_function()を実行しているときは、他のどのスレッドもcritical_section_functionを実行する必要はありません。

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


2
また、ミューテックスはプロセス間で共有できます。
コンフィギュレータ

14

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

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

StartCriticalSection();
    DoSomethingImportant();
    DoSomeOtherImportantThing();
EndCriticalSection();

1
ポスターは、相互排除を提供するだけのwin32 Criticalセクションオブジェクトのようなユーザーモード同期プリミティブについて話していたと思います。Linuxについては知りませんが、Windowsカーネルには、あなたが説明したように動作する重要な領域があります。
マイケル

1
なぜあなたが反対票を投じたのか分かりません。ありますコンセプトミューテックスのタイプであるクリティカルセクションと呼ばれるWindowsカーネルオブジェクト、異なっているあなたは正しく説明してきたクリティカルセクション、、のでは。OPは後者の定義について尋ねていたと思います。
Adam Rosenfield、

少なくとも、言語にとらわれないタグに戸惑いました。しかし、いずれにしても、これは、Microsoftがその実装を基本クラスと同じ名前にしたことで得られるものです。悪いコーディング練習!
Mikko Rantanen、

まあ、彼はできるだけ多くの詳細を求め、特にWindowsとLinuxは概念が良いように聞こえるので具体的に言った。+1--1も理解できなかった:/
Jason Coco

14

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

futexは、mutexの共有に使用する方法を使用して、プロセス間で共有することもできます。

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

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


6

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


6

私の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

一方、ミューテックスは、Windowsオブジェクトディレクトリに作成されたカーネルオブジェクト(ExMutantObjectType)です。ミューテックス操作は、ほとんどがカーネルモードで実装されています。たとえば、Mutexを作成すると、カーネルでnt!NtCreateMutantが呼び出されます。


Mutexオブジェクトを初期化して使用するプログラムがクラッシュするとどうなりますか?Mutexオブジェクトは自動的に割り当て解除されますか?いいえ、私は言うでしょう。正しい?
はAnkur

6
カーネルオブジェクトには参照カウントがあります。オブジェクトへのハンドルを閉じると参照カウントが減少し、0に達するとオブジェクトが解放されます。プロセスがクラッシュすると、そのハンドルはすべて自動的に閉じられるため、そのプロセスのみがハンドルを持つミューテックスは自動的に割り当て解除されます。
マイケル、

そして、これがクリティカルセクションオブジェクトがプロセスにバインドされている理由です。一方、ミューテックスはプロセス間で共有できます。
Sisir

2

マイケルからの素晴らしい答え。C ++ 11で導入されたミューテックスクラスの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未満です。私は非再帰的mutexのみをテストしたことに注意してください。CRITICAL_SECTIONオブジェクトは再帰的です(1つのスレッドが同じ回数だけ離れると、1つのスレッドが繰り返し入力できます)。


0

AC関数は、実際のパラメーターのみを使用する場合、再入可能と呼ばれます。

再入可能関数は、同時に複数のスレッドから呼び出すことができます。

再入可能関数の例:

int reentrant_function (int a, int b)
{
   int c;

   c = a + b;

   return c;
}

非再入可能関数の例:

int result;

void non_reentrant_function (int a, int b)
{
   int c;

   c = a + b;

   result = c;

}

C標準ライブラリstrtok()は再入可能ではなく、2つ以上のスレッドで同時に使用することはできません。

一部のプラットフォームSDKには、strtok_r()というstrtok()のリエントラントバージョンが付属しています。

エンリコ・ミリオーレ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.