it-swarm.com.de

Was ist der Unterschied zwischen Mutex und kritischem Bereich?

Bitte erläutern Sie unter Linux, Windows Perspektiven?

Ich programmiere in C #, würden diese beiden Begriffe einen Unterschied machen. Bitte poste so viel du kannst, mit Beispielen und so ....

Vielen Dank

128
ultraman

Bei Windows sind kritische Abschnitte leichter als Mutexe.

Mutexe können zwischen Prozessen geteilt werden, führen aber 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 bei Konflikten in den Kernel-Modus wechseln. Bei Konflikten treten sie in den Kernel ein, um auf ein Synchronisationsprimitiv (wie ein Ereignis oder ein Semaphor) zu warten.

Ich habe eine schnelle Beispiel-App geschrieben, die die Zeit zwischen den beiden vergleicht. Auf meinem System für 1.000.000 unkontrollierte Erfassungen und Veröffentlichungen dauert ein Mutex mehr als eine Sekunde. Ein kritischer Abschnitt benötigt ~ 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);
220
Michael

Aus theoretischer Sicht ist ein kritischer Abschnitt ein Teil des Codes, 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 sind viele Mutex-Implementierungen in Windows verfügbar. Sie unterscheiden sich aufgrund ihrer Implementierung hauptsächlich durch ihren Sperrgrad, ihren Geltungsbereich, ihre Kosten und ihre Leistung unter verschiedenen Konkurrenzsituationen. In CLR Inside Out - Verwenden der Parallelität für die Skalierbarkeit finden Sie eine Übersicht über die Kosten verschiedener Mutex-Implementierungen.

Verfügbare Synchronisationsprimitive.

Die lock(object) -Anweisung wird mit einem Monitor implementiert. Siehe MSDN als Referenz.

In den letzten Jahren wurde viel über nicht blockierende Synchronisation geforscht. Ziel ist es, Algorithmen ohne Sperren oder ohne Wartezeiten zu implementieren. In solchen Algorithmen hilft ein Prozess anderen Prozessen, ihre Arbeit zu beenden, so dass der Prozess schließlich seine Arbeit beenden kann. Infolgedessen kann ein Prozess seine Arbeit selbst dann beenden, wenn andere Prozesse, die versucht haben, eine Arbeit auszuführen, hängen bleiben. Wenn Sperren verwendet werden, werden die Sperren nicht freigegeben und andere Prozesse können nicht fortgesetzt werden.

84

Zusätzlich zu den anderen Antworten beziehen sich die folgenden Details auf wichtige Abschnitte unter Windows:

  • wenn keine Konflikte vorliegen, ist die Erfassung eines kritischen Abschnitts so einfach wie eine InterlockedCompareExchange -Operation
  • die kritische Abschnittsstruktur bietet Platz für einen Mutex. Es ist zunächst nicht zugeordnet
  • wenn zwischen Threads für einen kritischen Abschnitt ein Konflikt besteht, wird der Mutex zugewiesen und verwendet. Die Leistung des kritischen Abschnitts verschlechtert sich auf die des Mutex
  • wenn Sie mit einem hohen Konkurrenzdruck rechnen, können Sie den kritischen Bereich zuweisen und eine Anzahl von Drehungen angeben.
  • wenn in einem kritischen Abschnitt mit einer Anzahl von Drehungen ein Konflikt vorliegt, wird der Thread, der versucht, den kritischen Abschnitt zu erfassen, für diese Anzahl von Prozessorzyklen gedreht ("busy-wait"). Dies kann zu einer besseren Leistung führen als ein Ruhezustand, da die Anzahl der Zyklen zum Durchführen eines Kontextwechsels zu einem anderen Thread viel höher sein kann als die Anzahl der Zyklen, die der eigene Thread zum Freigeben des Mutex benötigt
  • wenn die Anzahl der Drehungen abläuft, wird der Mutex zugewiesen
  • wenn der besitzende Thread den kritischen Abschnitt freigibt, muss geprüft werden, ob der Mutex zugewiesen ist. Ist dies der Fall, wird der Mutex so eingestellt, dass ein wartender Thread freigegeben wird

Unter Linux haben sie meines Erachtens ein "Spin-Lock", das einem ähnlichen Zweck dient wie der kritische Abschnitt mit einer Spin-Zählung.

21

Critical Section und Mutex sind nicht betriebssystemspezifisch, ihre Konzepte für Multithreading/Multiprocessing.

Kritischer Abschnitt Ist ein Teil des Codes, der zu einem bestimmten Zeitpunkt nur von ihm selbst ausgeführt werden darf (zum Beispiel werden 5 Threads gleichzeitig ausgeführt und eine Funktion namens "critical_section_function" aktualisiert ein Array ... Sie Es sollen nicht alle 5 Threads gleichzeitig das Array aktualisieren. Wenn also critical_section_function () im Programm ausgeführt wird, muss keiner der anderen Threads die critical_section_function ausführen.

mutex * Mutex ist eine Methode zum Implementieren des kritischen Abschnittscodes (Betrachten Sie es als Token ... der Thread muss über diesen verfügen, um den critical_section_code ausführen zu können).

18
The Unknown

Das 'schnelle' Windows entspricht der kritischen Auswahl unter Linux und ist ein Futex , was für einen schnellen Benutzerraum-Mutex steht. Der Unterschied zwischen einem Futex und einem Mutex besteht darin, dass bei einem Futex der Kernel nur dann einbezogen wird, wenn eine Entscheidung erforderlich ist. Sie sparen sich also den Aufwand, jedes Mal, wenn der Atomzähler geändert wird, mit dem Kernel zu sprechen. Dies kann in einigen Anwendungen eine erhebliche Zeitersparnis beim Aushandeln von Sperren bedeuten.

Ein Futex kann auch von mehreren Prozessen gemeinsam genutzt werden, indem Sie die Mittel verwenden, mit denen Sie einen Mutex gemeinsam nutzen würden.

Leider können Futexes sehr schwierig zu implementieren (PDF) sein. (2018 Update, sie sind nicht annähernd so beängstigend wie im Jahr 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.

14
Tim Post

Ein Mutex ist ein Objekt, das ein Thread erfassen kann, wodurch verhindert wird, dass andere Threads es erfassen. Es ist ratsam, nicht obligatorisch; Ein Thread kann die Ressource verwenden, die der Mutex darstellt, ohne sie abzurufen.

Ein kritischer Abschnitt ist eine Codelänge, die vom Betriebssystem garantiert nicht unterbrochen wird. Im Pseudocode wäre es wie folgt:

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

Um meine 2 Cent hinzuzufügen, werden kritische Abschnitte als eine Struktur definiert und Operationen an ihnen werden im Benutzermoduskontext ausgeführt.

ntdll! _RTL_CRITICAL_SECTION 
 + 0x000 DebugInfo: Ptr32 _RTL_CRITICAL_SECTION_DEBUG 
 + 0x004 LockCount: Int4B 
 + 0x008 RecursionCount: Int4B 
 + 0x00c OwningThread [. + 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 am Ende nt! NtCreateMutant im Kernel auf.

6
Martin

In Windows ist ein kritischer Abschnitt für Ihren Prozess lokal. Auf einen Mutex kann prozessübergreifend zugegriffen werden. Grundsätzlich sind kritische Abschnitte viel billiger. Ich kann Linux nicht speziell kommentieren, aber auf einigen Systemen sind sie nur Aliase für die gleiche Sache.

6
Promit

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 mindestens vier Jahre jünger ist als seine, sodass Sie zwischen 2009 und 2013 Hinweise auf eine höhere Geschwindigkeit 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-Objekts CRITICAL_SECTION. Beachten Sie, dass ich nur den nicht-rekursiven Mutex getestet habe. CRITICAL_SECTION-Objekte sind rekursiv (ein Thread kann sie wiederholt eingeben, sofern er dieselbe Anzahl von Malen verlässt).

1
Stevens Miller