Antworten:
Schlösser dienen dem gegenseitigen Ausschluss. Wenn Sie sicherstellen möchten, dass ein Code atomar ist, setzen Sie eine Sperre um ihn. Sie könnten theoretisch ein binäres Semaphor verwenden, um dies zu tun, aber das ist ein Sonderfall.
Semaphoren und Bedingungsvariablen bauen auf dem gegenseitigen Ausschluss auf, der durch Sperren bereitgestellt wird, und werden für den synchronisierten Zugriff auf gemeinsam genutzte Ressourcen verwendet. Sie können für ähnliche Zwecke verwendet werden.
Eine Bedingungsvariable wird im Allgemeinen verwendet, um zu vermeiden, dass beim Warten auf die Verfügbarkeit einer Ressource viel gewartet wird (wiederholte Schleife beim Überprüfen einer Bedingung). Wenn Sie beispielsweise einen Thread (oder mehrere Threads) haben, der erst fortgesetzt werden kann, wenn eine Warteschlange leer ist, besteht der Ansatz des Wartens beim Warten darin, nur Folgendes zu tun:
//pseudocode
while(!queue.empty())
{
sleep(1);
}
Das Problem dabei ist, dass Sie Prozessorzeit verschwenden, indem dieser Thread den Zustand wiederholt überprüft. Warum nicht stattdessen eine Synchronisationsvariable haben, die signalisiert werden kann, um dem Thread mitzuteilen, dass die Ressource verfügbar ist?
//pseudocode
syncVar.lock.acquire();
while(!queue.empty())
{
syncVar.wait();
}
//do stuff with queue
syncVar.lock.release();
Vermutlich haben Sie irgendwo anders einen Thread, der Dinge aus der Warteschlange zieht. Wenn die Warteschlange leer ist, kann sie aufrufen syncVar.signal()
, um einen zufälligen Thread zu aktivieren, auf dem sie schläft syncVar.wait()
(oder es gibt normalerweise auch eine signalAll()
oder- broadcast()
Methode, um alle wartenden Threads zu aktivieren).
Im Allgemeinen verwende ich solche Synchronisationsvariablen, wenn ein oder mehrere Threads auf eine bestimmte Bedingung warten (z. B. wenn die Warteschlange leer ist).
Semaphore können ähnlich verwendet werden, aber ich denke, sie werden besser verwendet, wenn Sie eine gemeinsam genutzte Ressource haben, die verfügbar und nicht verfügbar sein kann, basierend auf einer ganzzahligen Anzahl verfügbarer Dinge. Semaphore eignen sich gut für Erzeuger- / Verbrauchersituationen, in denen Erzeuger Ressourcen zuweisen und Verbraucher sie verbrauchen.
Überlegen Sie, ob Sie einen Getränkeautomaten hatten. Es gibt nur eine Getränkemaschine und es handelt sich um eine gemeinsam genutzte Ressource. Sie haben einen Thread, bei dem es sich um einen Verkäufer (Hersteller) handelt, der für die Lagerhaltung der Maschine verantwortlich ist, und N Threads, bei denen es sich um Käufer (Verbraucher) handelt, die Limonaden aus der Maschine holen möchten. Die Anzahl der Limonaden in der Maschine ist der ganzzahlige Wert, der unser Semaphor antreibt.
Jeder Käufer- (Verbraucher-) Thread, der zur Getränkemaschine kommt, ruft die Semaphormethode down()
auf, um ein Getränk zu nehmen. Dadurch wird eine Limonade aus der Maschine entnommen und die Anzahl der verfügbaren Limonaden um 1 verringert. Wenn Limonaden verfügbar sind, läuft der Code down()
problemlos weiter an der Anweisung vorbei . Wenn keine Limonaden verfügbar sind, schläft der Thread hier und wartet darauf, benachrichtigt zu werden, wenn die Limonade wieder verfügbar ist (wenn sich mehr Limonaden in der Maschine befinden).
Der Thread des Verkäufers (Herstellers) würde im Wesentlichen darauf warten, dass die Getränkemaschine leer ist. Der Verkäufer wird benachrichtigt, wenn das letzte Soda aus der Maschine entnommen wird (und ein oder mehrere Verbraucher möglicherweise darauf warten, Soda herauszuholen). Der Verkäufer würde die Soda-Maschine mit der Semaphor- up()
Methode auffüllen , die verfügbare Anzahl von Soda würde jedes Mal erhöht und dadurch würden die wartenden Verbraucher-Threads benachrichtigt, dass mehr Soda verfügbar ist.
Die wait()
und signal()
Methoden einer Synchronisationsvariablen sind in der Regel in den down()
und up()
Operationen des Semaphors verborgen .
Sicher gibt es Überschneidungen zwischen den beiden Möglichkeiten. Es gibt viele Szenarien, in denen ein Semaphor oder eine Bedingungsvariable (oder eine Reihe von Bedingungsvariablen) Ihren Zwecken dienen könnten. Sowohl Semaphoren als auch Bedingungsvariablen sind einem Sperrobjekt zugeordnet, das sie zum Aufrechterhalten des gegenseitigen Ausschlusses verwenden. Anschließend bieten sie zusätzlich zur Sperre zusätzliche Funktionen zum Synchronisieren der Thread-Ausführung. Es liegt hauptsächlich an Ihnen, herauszufinden, welches für Ihre Situation am sinnvollsten ist.
Das ist nicht unbedingt die technischste Beschreibung, aber so macht es in meinem Kopf Sinn.
Lassen Sie uns zeigen, was sich unter der Haube befindet.
Die bedingte Variable ist im Wesentlichen eine Warteschlange , die das Blockieren von Warte- und Aufweckvorgängen unterstützt. Sie können also einen Thread in die Warteschlange stellen und seinen Status auf BLOCK setzen, einen Thread daraus entfernen und seinen Status auf BEREIT setzen.
Beachten Sie, dass zur Verwendung einer bedingten Variablen zwei weitere Elemente erforderlich sind:
Das Protokoll wird dann,
Semaphor ist im Wesentlichen ein Zähler + ein Mutex + eine Warteschlange. Und es kann so verwendet werden, wie es ist, ohne externe Abhängigkeiten. Sie können es entweder als Mutex oder als bedingte Variable verwenden.
Daher kann Semaphor als komplexere Struktur als bedingte Variable behandelt werden, während letztere leichter und flexibler ist.
Semaphore können verwendet werden, um den exklusiven Zugriff auf Variablen zu implementieren, sie sollen jedoch für die Synchronisation verwendet werden. Mutexe hingegen haben eine Semantik, die eng mit dem gegenseitigen Ausschluss zusammenhängt: Nur der Prozess, der die Ressource gesperrt hat, darf sie entsperren.
Leider können Sie keine Synchronisation mit Mutexen implementieren, deshalb haben wir Bedingungsvariablen. Beachten Sie auch, dass Sie mit Bedingungsvariablen alle wartenden Threads im selben Moment entsperren können, indem Sie die Broadcast-Entsperrung verwenden. Dies ist mit Semaphoren nicht möglich.
Semaphor- und Bedingungsvariablen sind sehr ähnlich und werden meist für die gleichen Zwecke verwendet. Es gibt jedoch geringfügige Unterschiede, die einen vorziehen könnten. Um beispielsweise eine Barrierensynchronisation zu implementieren, können Sie kein Semaphor verwenden. Eine Bedingungsvariable ist jedoch ideal.
Bei der Barrier-Synchronisierung möchten Sie, dass alle Ihre Threads warten, bis alle zu einem bestimmten Teil der Thread-Funktion gelangt sind. Dies kann implementiert werden, indem eine statische Variable verwendet wird, die anfänglich der Wert der gesamten Threads ist, die von jedem Thread dekrementiert werden, wenn er diese Barriere erreicht. Dies würde bedeuten, dass jeder Thread schlafen soll, bis der letzte eintrifft. Ein Semaphor würde genau das Gegenteil bewirken! Mit einem Semaphor würde jeder Thread weiterlaufen und der letzte Thread (der den Semaphorwert auf 0 setzt) wird in den Ruhezustand versetzt.
Eine Bedingungsvariable hingegen ist ideal. Wenn jeder Thread die Barriere erreicht, prüfen wir, ob unser statischer Zähler Null ist. Wenn nicht, setzen wir den Thread mit der Bedingungsvariablen-Wartefunktion auf Ruhezustand. Wenn der letzte Thread die Barriere erreicht, wird der Zählerwert auf Null dekrementiert und dieser letzte Thread ruft die Bedingungsvariablen-Signalfunktion auf, die alle anderen Threads aufweckt!
Ich lege Bedingungsvariablen unter Monitorsynchronisation ab. Ich habe Semaphoren und Monitore im Allgemeinen als zwei verschiedene Synchronisationsstile gesehen. Es gibt Unterschiede zwischen den beiden in Bezug darauf, wie viele Statusdaten inhärent gespeichert sind und wie Sie Code modellieren möchten - aber es gibt wirklich kein Problem, das von dem einen, aber nicht vom anderen gelöst werden kann.
Ich neige dazu, in Richtung Monitorform zu codieren. In den meisten Sprachen, in denen ich arbeite, kommt es auf Mutexe, Bedingungsvariablen und einige Hintergrundzustandsvariablen an. Aber auch Semaphoren würden den Job machen.
Die mutex
und conditional variables
werden von geerbt semaphore
.
mutex
diesemaphore
Verwendungen zwei Zustände: 0, 1condition variables
den semaphore
Verwendungszähler.Sie sind wie syntaktischer Zucker