Bitte erklären Sie aus Linux-, Windows-Perspektiven?
Ich programmiere in C #, würden diese beiden Begriffe einen Unterschied machen. Bitte poste so viel wie möglich mit Beispielen und dergleichen ....
Vielen Dank
Bitte erklären Sie aus Linux-, Windows-Perspektiven?
Ich programmiere in C #, würden diese beiden Begriffe einen Unterschied machen. Bitte poste so viel wie möglich mit Beispielen und dergleichen ....
Vielen Dank
Antworten:
Unter Windows sind kritische Abschnitte leichter als Mutexe.
Mutexe können von Prozessen gemeinsam genutzt werden, führen jedoch immer zu einem Systemaufruf an den Kernel, der einen gewissen Overhead hat.
Kritische Abschnitte können nur innerhalb eines Prozesses verwendet werden, haben jedoch den Vorteil, dass sie nur im Konfliktfall in den Kernel-Modus wechseln. Unerwünschte Erfassungen, die der übliche Fall sein sollten, sind unglaublich schnell. Im Streitfall betreten sie den Kernel, um auf ein Synchronisationsprimitiv (wie ein Ereignis oder ein Semaphor) zu warten.
Ich habe eine kurze Beispiel-App geschrieben, die die Zeit zwischen den beiden vergleicht. Auf meinem System für 1.000.000 unerwünschte Erfassungen und Freigaben dauert ein Mutex über eine Sekunde. Ein kritischer Abschnitt dauert ~ 50 ms für 1.000.000 Erfassungen.
Hier ist der Testcode. Ich habe diesen ausgeführt und ähnliche Ergebnisse erhalten, wenn Mutex der erste oder zweite ist, sodass wir keine anderen Effekte sehen.
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);
Aus theoretischer Sicht ist ein kritischer Abschnitt ein Code, der nicht von mehreren Threads gleichzeitig ausgeführt werden darf, da der Code auf gemeinsam genutzte Ressourcen zugreift.
Ein Mutex ist ein Algorithmus (und manchmal der Name einer Datenstruktur), der zum Schutz kritischer Abschnitte verwendet wird.
Semaphoren und Monitore sind übliche Implementierungen eines Mutex.
In der Praxis stehen in Windows viele Mutex-Implementierungen zur Verfügung. Sie unterscheiden sich hauptsächlich als Folge ihrer Implementierung durch ihren Sperrgrad, ihren Umfang, ihre Kosten und ihre Leistung unter verschiedenen Streitniveaus. Ein Diagramm der Kosten verschiedener Mutex-Implementierungen finden Sie unter CLR Inside Out - Verwenden der Parallelität für die Skalierbarkeit .
Verfügbare Synchronisationsprimitive.
Die lock(object)
Anweisung wird mithilfe von a implementiert Monitor
- siehe MSDN als Referenz.
In den letzten Jahren wurde viel über die nicht blockierende Synchronisation geforscht . Ziel ist es, Algorithmen sperrfrei oder wartungsfrei zu implementieren. In solchen Algorithmen hilft ein Prozess anderen Prozessen, ihre Arbeit zu beenden, so dass der Prozess seine Arbeit endgültig beenden kann. Infolgedessen kann ein Prozess seine Arbeit auch dann beenden, wenn andere Prozesse, die versucht haben, einige Arbeiten auszuführen, hängen bleiben. Bei Verwendung von Sperren würden sie ihre Sperren nicht freigeben und verhindern, dass andere Prozesse fortgesetzt werden.
Zusätzlich zu den anderen Antworten gelten die folgenden Details speziell für kritische Abschnitte in Windows:
InterlockedCompareExchange
OperationUnter Linux denke ich, dass sie eine "Spin-Sperre" haben, die einen ähnlichen Zweck erfüllt wie der kritische Abschnitt mit einer Spin-Anzahl.
Critical Section und Mutex sind nicht betriebssystemspezifisch, ihre Konzepte von Multithreading / Multiprocessing.
Kritischer Abschnitt Ist ein Code, der zu einem bestimmten Zeitpunkt nur von selbst ausgeführt werden darf (z. B. werden 5 Threads gleichzeitig ausgeführt und eine Funktion namens "Critical_Section_function", die ein Array aktualisiert ... Sie möchten nicht alle 5 Threads Aktualisieren des Arrays auf einmal. Wenn das Programm also Critical_section_function () ausführt, muss keiner der anderen Threads seine Critical_section_function ausführen.
mutex * Mutex ist eine Möglichkeit, den Code für kritische Abschnitte zu implementieren (stellen Sie ihn sich wie ein Token vor ... der Thread muss ihn besitzen, um den Code für den kritischen Abschnitt auszuführen).
Ein Mutex ist ein Objekt, das ein Thread erfassen kann, wodurch verhindert wird, dass andere Threads ihn erfassen. Es ist beratend, nicht obligatorisch; Ein Thread kann die Ressource verwenden, die der Mutex darstellt, ohne sie zu erwerben.
Ein kritischer Abschnitt ist eine Codelänge, die vom Betriebssystem garantiert wird, dass sie nicht unterbrochen wird. Im Pseudocode wäre es wie:
StartCriticalSection();
DoSomethingImportant();
DoSomeOtherImportantThing();
EndCriticalSection();
Das "schnelle" Windows, das der kritischen Auswahl unter Linux entspricht, wäre ein Futex , der für schnellen Mutex für den Benutzerbereich steht. Der Unterschied zwischen einem Futex und einem Mutex besteht darin, dass bei einem Futex der Kernel nur dann beteiligt wird, wenn ein Schiedsverfahren erforderlich ist. Sie sparen also den Aufwand, jedes Mal, wenn der Atomzähler geändert wird, mit dem Kernel zu sprechen. Dies kann in einigen Anwendungen erheblich Zeit beim Aushandeln von Sperren sparen .
Ein Futex kann auch von Prozessen gemeinsam genutzt werden, indem Sie die Mittel verwenden, die Sie zum Teilen eines Mutex verwenden würden.
Leider kann die Implementierung von Futexen sehr schwierig sein (PDF). (Update 2018, sie sind bei weitem nicht so beängstigend wie 2009).
Darüber hinaus ist es auf beiden Plattformen ziemlich gleich. Sie führen atomare, tokengesteuerte Aktualisierungen an einer gemeinsam genutzten Struktur auf eine Weise durch, die (hoffentlich) keinen Hunger verursacht. Was bleibt, ist einfach die Methode, um dies zu erreichen.
In Windows ist ein kritischer Abschnitt lokal für Ihren Prozess. Ein Mutex kann prozessübergreifend gemeinsam genutzt / aufgerufen werden. Grundsätzlich sind kritische Abschnitte viel billiger. Ich kann Linux nicht speziell kommentieren, aber auf einigen Systemen sind sie nur Aliase für dasselbe.
Um meine 2 Cent hinzuzufügen, werden kritische Abschnitte als Struktur definiert und Operationen im Benutzermodus ausgeführt.
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
Während Mutex Kernelobjekte (ExMutantObjectType) sind, die im Windows-Objektverzeichnis erstellt wurden. Mutex-Operationen werden meist im Kernel-Modus implementiert. Wenn Sie beispielsweise einen Mutex erstellen, rufen Sie im Kernel nt! NtCreateMutant auf.
Tolle Antwort von Michael. Ich habe einen dritten Test für die in C ++ 11 eingeführte Mutex-Klasse hinzugefügt. Das Ergebnis ist etwas interessant und unterstützt immer noch seine ursprüngliche Bestätigung von CRITICAL_SECTION-Objekten für einzelne Prozesse.
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);
Meine Ergebnisse waren 217, 473 und 19 (beachten Sie, dass mein Zeitverhältnis für die letzten beiden ungefähr mit dem von Michael vergleichbar ist, aber meine Maschine ist mindestens vier Jahre jünger als seine, sodass Sie Hinweise auf eine höhere Geschwindigkeit zwischen 2009 und 2013 sehen können , als der XPS-8700 herauskam). Die neue Mutex-Klasse ist doppelt so schnell wie der Windows-Mutex, aber immer noch weniger als ein Zehntel der Geschwindigkeit des Windows CRITICAL_SECTION-Objekts. Beachten Sie, dass ich nur den nicht rekursiven Mutex getestet habe. CRITICAL_SECTION-Objekte sind rekursiv (ein Thread kann sie wiederholt eingeben, vorausgesetzt, er verlässt sie gleich oft).
AC-Funktionen werden als Wiedereintritt bezeichnet, wenn nur die tatsächlichen Parameter verwendet werden.
Wiedereintrittsfunktionen können von mehreren Threads gleichzeitig aufgerufen werden.
Beispiel für eine Wiedereintrittsfunktion:
int reentrant_function (int a, int b)
{
int c;
c = a + b;
return c;
}
Beispiel für eine nicht wiedereintretende Funktion:
int result;
void non_reentrant_function (int a, int b)
{
int c;
c = a + b;
result = c;
}
Die C-Standardbibliothek strtok () ist nicht wiedereintrittsfähig und kann nicht von zwei oder mehr Threads gleichzeitig verwendet werden.
Einige Plattform-SDKs enthalten die wiedereintrittsfähige Version von strtok () mit dem Namen strtok_r ().
Enrico Migliore