Bedingte Variable gegen Semaphor

Antworten:

207

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.

Brent schreibt Code
quelle
9
Tolle Antwort, möchte ich von anderen hinzufügen, also Antworten: Semaphor wird verwendet, um die Anzahl der ausgeführten Threads zu steuern. Es wird einen festen Satz von Ressourcen geben. Die Ressourcenanzahl wird jedes Mal dekrementiert, wenn ein Thread denselben besitzt. Wenn die Semaphoranzahl 0 erreicht, dürfen keine anderen Threads die Ressource abrufen. Die Threads werden blockiert, bis andere Threads Ressourcen freigeben. Kurz gesagt, der Hauptunterschied besteht darin, wie viele Threads die Ressource gleichzeitig abrufen dürfen. Mutex - es ist EINS. Semaphor - sein DEFINED_COUNT (so viele wie Semaphor zählen)
Berkeley
10
Nur um herauszufinden, warum es diese while-Schleife anstelle eines einfachen if gibt: etwas, das als spurios wakeup bezeichnet wird . Zitiert diesen Wikipedia-Artikel : "Einer der Gründe dafür ist ein falsches Aufwecken; das heißt, ein Thread könnte aus seinem Wartezustand geweckt werden, obwohl kein Thread die Bedingungsvariable signalisiert"
Vladislavs Burakovs
3
@VladislavsBurakovs Guter Punkt! Ich denke, es ist auch hilfreich für den Fall, dass eine Sendung mehr Threads aufweckt, als Ressourcen verfügbar sind (z. B. weckt Broadcast 3 Threads auf, aber es befinden sich nur 2 Elemente in der Warteschlange).
Brent schreibt Code
Ich wünschte, ich würde deine Antwort positiv bewerten, bis die Warteschlange voll ist;) Perfekte Antwort. Dieser Code könnte helfen, Semaphoren herauszufinden csc.villanova.edu/~mdamian/threads/PC.htm
Mohamad-Jaafar NEHME
3
@VladislavsBurakovs Um ein wenig zu verdeutlichen, ist der Grund dafür, dass die Bedingung für einen gerade aufgeweckten Thread möglicherweise immer noch falsch ist (was zu einem falschen Aufwecken führt), dass möglicherweise ein Kontextwechsel stattgefunden hat, bevor der Thread die Möglichkeit hatte, die Bedingung zu überprüfen wieder, wo ein anderer geplanter Thread diese Bedingung falsch gemacht hat. Dies ist ein Grund, den ich für ein falsches Aufwachen kenne, ich weiß nicht, ob es mehr gibt.
Max
52

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:

  • eine Bedingung (normalerweise implementiert durch Überprüfen eines Flags oder eines Zählers)
  • Ein Mutex, der den Zustand schützt

Das Protokoll wird dann,

  1. Mutex erwerben
  2. Zustand prüfen
  3. Mutex blockieren und freigeben, wenn die Bedingung erfüllt ist, andernfalls Mutex freigeben

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.

Cucufrog
quelle
Mutex kann als Bedingungsvariable angesehen werden. Die Bedingung ist, ob sie gehalten wird oder nicht.
李 李
18

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.

Dacav
quelle
9

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!

Danielle
quelle
1

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.

Justin R.
quelle
2
Dies wäre eine bessere Antwort, wenn Sie erklären würden, was "Monitorform" ist.
Steven Lu
0

Die mutexund conditional variableswerden von geerbt semaphore.

  • Für mutexdiesemaphore Verwendungen zwei Zustände: 0, 1
  • Für condition variablesden semaphore Verwendungszähler.

Sie sind wie syntaktischer Zucker

Aqua
quelle
In der C ++ - Standardbibliothek sind dies alle Distriktobjekte, die alle mithilfe plattformspezifischer APIs implementiert werden. Sicherlich wird ein Semaphor die Anzahl der signalisierten Signale entsperren. Die Bedingungsvariable wird möglicherweise mehrmals signalisiert, aber nur einmal entsperrt. Aus diesem Grund verwendet der Wair einen Mutex als Parameter.
Doron vor