Es ist nur die Art und Weise, wie Bedingungsvariablen implementiert werden (oder ursprünglich).
Der Mutex wird verwendet, um die Bedingungsvariable selbst zu schützen . Deshalb muss es gesperrt sein, bevor Sie warten.
Durch das Warten wird der Mutex "atomar" entsperrt, sodass andere auf die Bedingungsvariable zugreifen können (zur Signalisierung). Wenn dann die Bedingungsvariable signalisiert oder gesendet wird, werden einer oder mehrere der Threads auf der Warteliste aufgeweckt und der Mutex wird für diesen Thread wieder magisch gesperrt.
In der Regel wird die folgende Operation mit Bedingungsvariablen angezeigt, die ihre Funktionsweise veranschaulicht. Das folgende Beispiel ist ein Arbeitsthread, der über ein Signal an eine Bedingungsvariable Arbeit erhält.
thread:
initialise.
lock mutex.
while thread not told to stop working:
wait on condvar using mutex.
if work is available to be done:
do the work.
unlock mutex.
clean up.
exit thread.
Die Arbeit wird innerhalb dieser Schleife erledigt, vorausgesetzt, es sind einige verfügbar, wenn die Wartezeit zurückkehrt. Wenn der Thread markiert wurde, um die Arbeit zu beenden (normalerweise durch einen anderen Thread, der die Beendigungsbedingung festlegt und dann die Bedingungsvariable aufruft, um diesen Thread aufzuwecken), wird die Schleife beendet, der Mutex wird entsperrt und dieser Thread wird beendet.
Der obige Code ist ein Einzelverbrauchermodell, da der Mutex während der Arbeit gesperrt bleibt. Für eine Multi-Consumer-Variante können Sie als Beispiel Folgendes verwenden :
thread:
initialise.
lock mutex.
while thread not told to stop working:
wait on condvar using mutex.
if work is available to be done:
copy work to thread local storage.
unlock mutex.
do the work.
lock mutex.
unlock mutex.
clean up.
exit thread.
Dies ermöglicht anderen Verbrauchern, Arbeit zu erhalten, während dieser Arbeit erledigt.
Die Bedingungsvariable entlastet Sie von der Last, eine Bedingung abzufragen, anstatt dass ein anderer Thread Sie benachrichtigt, wenn etwas passieren muss. Ein anderer Thread kann diesem Thread mitteilen, dass diese Arbeit wie folgt verfügbar ist:
lock mutex.
flag work as available.
signal condition variable.
unlock mutex.
Die überwiegende Mehrheit der häufig fälschlicherweise als unechte Aufweckvorgänge bezeichneten Probleme war im Allgemeinen immer darauf zurückzuführen, dass innerhalb ihres pthread_cond_wait
Anrufs (Broadcast) mehrere Threads signalisiert wurden. Man kehrte mit dem Mutex zurück, erledigte die Arbeit und wartete erneut.
Dann konnte der zweite signalisierte Thread herauskommen, wenn keine Arbeit zu erledigen war. Sie mussten also eine zusätzliche Variable haben, die angibt, dass die Arbeit erledigt werden soll (dies war von Natur aus mutexgeschützt mit dem Condvar / Mutex-Paar hier - andere Threads mussten den Mutex sperren, bevor er geändert wurde).
Es war technisch möglich, dass ein Thread von einer Wartezeit zurückkehrt, ohne von einem anderen Prozess ausgelöst zu werden (dies ist ein echtes falsches Aufwecken), aber in all meinen vielen Jahren, in denen ich an Pthreads gearbeitet habe, sowohl bei der Entwicklung / beim Service des Codes als auch als Benutzer von ihnen habe ich nie eine davon erhalten. Vielleicht lag das nur daran, dass HP eine anständige Implementierung hatte :-)
In jedem Fall behandelte derselbe Code, der den fehlerhaften Fall behandelt hat, auch echte falsche Aufweckvorgänge, da das Flag für verfügbare Arbeit für diese nicht gesetzt wäre.
do something
in derwhile
Schleife?Eine Bedingungsvariable ist ziemlich begrenzt, wenn Sie nur eine Bedingung signalisieren können. Normalerweise müssen Sie einige Daten verarbeiten, die sich auf die signalisierte Bedingung beziehen. Signalisierung / Aufwecken müssen atomar erfolgen, um dies zu erreichen, ohne Rennbedingungen einzuführen, oder übermäßig komplex sein
pthreads können aus technischen Gründen auch zu einem falschen Aufwecken führen . Das bedeutet, dass Sie ein Prädikat überprüfen müssen, damit Sie sicher sein können, dass der Zustand tatsächlich signalisiert wurde - und dies von einem falschen Aufwecken unterscheiden können. Das Überprüfen einer solchen Bedingung hinsichtlich des Wartens darauf muss geschützt werden. Daher muss eine Bedingungsvariable eine Möglichkeit zum atomaren Warten / Aufwachen haben, während ein Mutex gesperrt / entsperrt wird, der diese Bedingung schützt.
Stellen Sie sich ein einfaches Beispiel vor, in dem Sie benachrichtigt werden, dass einige Daten erstellt wurden. Möglicherweise hat ein anderer Thread einige Daten erstellt, die Sie möchten, und einen Zeiger auf diese Daten gesetzt.
Stellen Sie sich einen Producer-Thread vor, der über einen Zeiger 'some_data' einige Daten an einen anderen Consumer-Thread weitergibt.
Sie würden natürlich eine Menge Rennbedingungen bekommen, was wäre, wenn der andere Thread
some_data = new_data
direkt nach dem Aufwachen, aber bevor Sie es tun würdendata = some_data
Sie können auch keinen eigenen Mutex erstellen, um diesen Fall zu schützen .eg
Funktioniert nicht, es besteht immer noch die Möglichkeit einer Rennbedingung zwischen dem Aufwachen und dem Ergreifen des Mutex. Das Platzieren des Mutex vor dem pthread_cond_wait hilft Ihnen nicht, da Sie den Mutex jetzt während des Wartens halten - dh der Produzent wird niemals in der Lage sein, den Mutex zu greifen. (Beachten Sie, dass Sie in diesem Fall eine zweite Bedingungsvariable erstellen können, um dem Produzenten zu signalisieren, dass Sie damit fertig sind.
some_data
Dies wird jedoch komplex, insbesondere wenn Sie viele Produzenten / Konsumenten wünschen.)Daher benötigen Sie eine Möglichkeit, den Mutex atomar freizugeben / zu ergreifen, wenn Sie warten / aus dem Zustand aufwachen. Das ist es, was pthread-Bedingungsvariablen tun, und hier ist, was Sie tun würden:
(Der Produzent müsste natürlich die gleichen Vorsichtsmaßnahmen treffen, immer 'some_data' mit dem gleichen Mutex schützen und sicherstellen, dass some_data nicht überschrieben wird, wenn some_data aktuell ist! = NULL)
quelle
while (some_data != NULL)
nicht eine Do-While-Schleife sein, damit sie mindestens einmal auf die Bedingungsvariable wartet?while(some_data != NULL)
seinwhile(some_data == NULL)
?POSIX-Bedingungsvariablen sind zustandslos. Es liegt also in Ihrer Verantwortung, den Staat aufrechtzuerhalten. Da sowohl wartende Threads als auch Threads, die anderen Threads anweisen, nicht mehr zu warten, auf den Status zugreifen, muss er durch einen Mutex geschützt werden. Wenn Sie glauben, dass Sie Bedingungsvariablen ohne Mutex verwenden können, haben Sie nicht verstanden, dass Bedingungsvariablen zustandslos sind.
Bedingungsvariablen basieren auf einer Bedingung. Threads, die auf eine Bedingungsvariable warten, warten auf eine Bedingung. Threads, die Bedingungsvariablen signalisieren, ändern diese Bedingung. Beispielsweise wartet ein Thread möglicherweise auf das Eintreffen einiger Daten. Ein anderer Thread bemerkt möglicherweise, dass die Daten angekommen sind. "Die Daten sind angekommen" ist die Bedingung.
Hier ist die klassische Verwendung einer bedingten Bedingungsvariablen:
Sehen Sie, wie der Thread auf die Arbeit wartet. Die Arbeit ist durch einen Mutex geschützt. Das Warten gibt den Mutex frei, damit ein anderer Thread diesem Thread etwas Arbeit geben kann. So würde es signalisiert werden:
Beachten Sie, dass Sie den Mutex benötigen , um die Arbeitswarteschlange zu schützen. Beachten Sie, dass die Bedingungsvariable selbst keine Ahnung hat, ob Arbeit vorhanden ist oder nicht. Das heißt, eine Bedingungsvariable muss einer Bedingung zugeordnet sein, diese Bedingung muss von Ihrem Code verwaltet werden, und da sie von Threads gemeinsam genutzt wird, muss sie durch einen Mutex geschützt werden.
quelle
Nicht alle Bedingungsvariablenfunktionen erfordern einen Mutex: Nur die Warteoperationen. Die Signal- und Rundfunkoperationen erfordern keinen Mutex. Eine Bedingungsvariable ist auch keinem bestimmten Mutex dauerhaft zugeordnet. Der externe Mutex schützt die Bedingungsvariable nicht. Wenn eine Bedingungsvariable einen internen Status hat, z. B. eine Warteschlange wartender Threads, muss dies durch eine interne Sperre innerhalb der Bedingungsvariablen geschützt werden.
Die Warteoperationen bringen eine Bedingungsvariable und einen Mutex zusammen, weil:
Aus diesem Grund verwendet die Warteoperation sowohl den Mutex als auch die Bedingung als Argumente: Damit kann die atomare Übertragung eines Threads vom Besitz des Mutex zum Warten verwaltet werden, sodass der Thread nicht der verlorenen Wecklaufbedingung zum Opfer fällt .
Eine verlorene Wakeup-Race-Bedingung tritt auf, wenn ein Thread einen Mutex aufgibt und dann auf ein zustandsloses Synchronisationsobjekt wartet, jedoch auf eine Weise, die nicht atomar ist: Es gibt ein Zeitfenster, in dem der Thread nicht mehr gesperrt ist und hat noch nicht begonnen, auf das Objekt zu warten. Während dieses Fensters kann ein anderer Thread eingehen, die erwartete Bedingung erfüllen, die zustandslose Synchronisation signalisieren und dann verschwinden. Das zustandslose Objekt erinnert sich nicht daran, dass es signalisiert wurde (es ist zustandslos). Dann geht der ursprüngliche Thread auf dem zustandslosen Synchronisationsobjekt in den Ruhezustand und wird nicht aktiviert, obwohl die Bedingung, die er benötigt, bereits erfüllt ist: verlorenes Aufwecken.
Die Wartefunktionen der Bedingungsvariablen vermeiden das verlorene Aufwecken, indem sie sicherstellen, dass der aufrufende Thread registriert ist, um das Aufwecken zuverlässig abzufangen, bevor er den Mutex aufgibt. Dies wäre unmöglich, wenn die Bedingungsfunktion für Bedingungsvariablen den Mutex nicht als Argument verwenden würde.
quelle
pthread_cond_broadcast
und diepthread_cond_signal
Operationen (um die es in dieser SO-Frage geht) nehmen nicht einmal den Mutex als Argument; nur die Bedingung. Die POSIX-Spezifikation ist hier . Der Mutex wird nur in Bezug auf das erwähnt, was in den wartenden Threads passiert, wenn sie aufwachen.Ich finde die anderen Antworten nicht so präzise und lesbar wie diese Seite . Normalerweise sieht der Wartecode ungefähr so aus:
Es gibt drei Gründe, das
wait()
in einen Mutex zu wickeln :signal()
vor demwait()
und wir würden dieses Aufwachen verpassen.check()
hängt es von Änderungen an einem anderen Thread ab, daher müssen Sie ihn trotzdem gegenseitig ausschließen.Der dritte Punkt ist nicht immer ein Problem - der historische Kontext ist vom Artikel aus mit diesem Gespräch verknüpft .
In Bezug auf diesen Mechanismus werden häufig falsche Aufweckvorgänge erwähnt (dh der wartende Thread wird geweckt, ohne
signal()
aufgerufen zu werden). Solche Ereignisse werden jedoch von der Schleife behandeltcheck()
.quelle
Bedingungsvariablen sind mit einem Mutex verknüpft, da nur so die Rasse vermieden werden kann, die er vermeiden soll.
Zu diesem Zeitpunkt gibt es keinen Thread, der die Bedingungsvariable signalisiert, sodass Thread1 für immer warten wird, obwohl die protectedReadyToRunVariable angibt, dass es einsatzbereit ist!
Die einzige Möglichkeit, dies zu umgehen, besteht darin, dass Bedingungsvariablen den Mutex atomar freigeben und gleichzeitig auf die Bedingungsvariable warten. Aus diesem Grund erfordert die Funktion cond_wait einen Mutex
quelle
Der Mutex soll gesperrt sein, wenn Sie anrufen
pthread_cond_wait
. Wenn Sie es atomar aufrufen, wird sowohl der Mutex entsperrt als auch die Bedingung blockiert. Sobald der Zustand signalisiert ist, sperrt er ihn wieder atomar und kehrt zurück.Dies ermöglicht die Implementierung einer vorhersagbaren Planung, falls gewünscht, indem der Thread, der die Signalisierung durchführen würde, warten kann, bis der Mutex freigegeben wird, um seine Verarbeitung durchzuführen, und dann den Zustand signalisieren kann.
quelle
Ich habe eine Übung im Unterricht gemacht, wenn Sie ein echtes Beispiel für eine Bedingungsvariable wollen:
quelle
Es scheint eher eine spezifische Entwurfsentscheidung als eine konzeptionelle Notwendigkeit zu sein.
Laut den pthreads-Dokumenten ist der Grund dafür, dass der Mutex nicht getrennt wurde, dass es eine signifikante Leistungsverbesserung gibt, wenn sie kombiniert werden, und sie erwarten, dass aufgrund der allgemeinen Rennbedingungen, wenn Sie keinen Mutex verwenden, dies fast immer sowieso getan wird.
https://linux.die.net/man/3/pthread_cond_wait
quelle
Es gibt eine Menge Exegesen darüber, aber ich möchte es mit einem folgenden Beispiel verkörpern.
Was ist los mit dem Code-Snippet? Denken Sie einfach etwas nach, bevor Sie fortfahren.
Das Problem ist wirklich subtil. Wenn der Elternteil
thr_parent()
den Wert von aufruft und dann überprüftdone
, sieht er, dass dies der Fall ist,0
und versucht daher, einzuschlafen. Doch kurz bevor der Anruf wartet, um schlafen zu gehen, wird der Elternteil zwischen den Zeilen 6-7 unterbrochen und das Kind rennt. Das Kind ändert die Statusvariabledone
in1
und signalisiert, aber es wartet kein Thread und somit wird kein Thread geweckt. Wenn der Elternteil wieder rennt, schläft er für immer, was wirklich ungeheuerlich ist.Was ist, wenn sie ausgeführt werden, während die Schlösser einzeln erworben wurden?
quelle