Linux、Windowsの観点から説明してください。
私はC#でプログラミングしていますが、これら2つの用語は違いますか?例などを含めて、できる限り投稿してください。
ありがとう
Linux、Windowsの観点から説明してください。
私はC#でプログラミングしていますが、これら2つの用語は違いますか?例などを含めて、できる限り投稿してください。
ありがとう
回答:
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);
理論的な観点から見ると、クリティカルセクションはコードが共有リソースにアクセスするため、一度に複数のスレッドで実行してはならないコードです。
ミューテックスは、クリティカルセクションを保護するために使用されている(時々 、データ構造の名前)アルゴリズムです。
実際には、ウィンドウで利用可能な多くのミューテックス実装があります。それらは、ロックのレベル、スコープ、コスト、および異なるレベルの競合下でのパフォーマンスによって、実装の結果として主に異なります。さまざまなミューテックス実装のコストのグラフについては、CLR Inside Out-同時実行性を使用したスケーラビリティを参照してください。
利用可能な同期プリミティブ。
lock(object)
文は使用して実装されているMonitor
参照- MSDNを参照するために。
ここ数年、非ブロッキング同期について多くの研究が行われています。目標は、ロックフリーまたは待機フリーの方法でアルゴリズムを実装することです。このようなアルゴリズムでは、プロセスは他のプロセスが作業を完了するのを助け、プロセスが最終的に作業を完了できるようにします。その結果、あるプロセスを実行しようとした他のプロセスがハングした場合でも、プロセスはそのワークを終了できます。ロックを使用すると、ロックが解放されず、他のプロセスが続行できなくなります。
他の回答に加えて、次の詳細はウィンドウの重要なセクションに固有です。
InterlockedCompareExchange
操作と同じくらい簡単ですLinuxでは、スピンカウントのクリティカルセクションと同様の目的で機能する「スピンロック」があると思います。
Critical SectionとMutexはオペレーティングシステム固有ではなく、マルチスレッド/マルチプロセッシングの概念です。
クリティカルセクション は、常に自分でのみ実行する必要があるコードの一部です(たとえば、同時に実行される5つのスレッドと、配列を更新する「critical_section_function」と呼ばれる関数があります... 5つのスレッドすべてが必要ではありません。配列を一度に更新するため、プログラムがcritical_section_function()を実行しているときは、他のどのスレッドもcritical_section_functionを実行する必要はありません。
mutex * Mutexは、クリティカルセクションコードを実装する方法です(トークンのように考えると...スレッドはcritical_section_codeを実行するためにそれを所有している必要があります)
mutexは、スレッドが取得できるオブジェクトであり、他のスレッドがそれを取得できないようにします。これは助言であり、必須ではありません。スレッドは、mutexが表すリソースを獲得せずに使用できます。
クリティカルセクションは、オペレーティングシステムによって中断されないことが保証されているコードの長さです。疑似コードでは、次のようになります。
StartCriticalSection();
DoSomethingImportant();
DoSomeOtherImportantThing();
EndCriticalSection();
Linuxでの重要な選択と同等の「高速」Windowsはfutexであり、これは高速ユーザー空間ミューテックスを表しています。futexとmutexの違いは、futexの場合、アービトレーションが必要な場合にのみカーネルが関与するため、アトミックカウンターが変更されるたびにカーネルと通信するオーバーヘッドを節約できることです。これにより、一部のアプリケーションでロックをネゴシエートする時間を大幅に節約できます。
futexは、mutexの共有に使用する方法を使用して、プロセス間で共有することもできます。
残念ながら、futexは実装が非常に難しい場合があります(PDF)。(2018年の更新、彼らは2009年ほど怖くはありません)。
それ以外は、どちらのプラットフォームでもほぼ同じです。(うまくいけば)飢餓を引き起こさない方法で、共有構造に対してアトミックなトークン駆動の更新を行っています。残っているのは、単にそれを達成する方法です。
私の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が呼び出されます。
マイケルからの素晴らしい答え。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つのスレッドが繰り返し入力できます)。
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()のリエントラントバージョンが付属しています。
エンリコ・ミリオーレ