Scheinwecken — Warum Bedingungsvariablen „ohne Benachrichtigung“ aufwachen und wie man unter Windows richtig wartet
· Aktualisiert am: · Go Komura · Windows, Multithreading, Bedingungsvariablen, Synchronisierung, C++, C#, Win32 API, Fehleruntersuchung
Änderungsverlauf (Erstfassung, veröffentlicht am 22. Aug 2026)
- Erstveröffentlichung
Diesen Artikel zitieren(DOI (registriertes Archiv): 10.5281/zenodo.22176627)
Die folgenden DOIs verweisen auf bereits archivierte Versionen, die vom aktuellen Text abweichen können. Verwenden Sie die URL dieser Seite, um auf den aktuellen Text zu verweisen.
Go Komura (2026). Scheinwecken — Warum Bedingungsvariablen „ohne Benachrichtigung“ aufwachen und wie man unter Windows richtig wartet. KomuraSoft LLC. https://comcomponent.com/de/blog/windows-condition-variable-spurious-wakeup/
- DOI (registriertes Archiv)
- 10.5281/zenodo.22176627
- DOI (zuletzt registrierte Version)
- 10.5281/zenodo.22176628
„Wir haben die Daten eingelegt und dann benachrichtigt, und trotzdem liest der Worker eine leere Warteschlange und stürzt ab.“ „Wir haben benachrichtigt, und trotzdem kehrt der Thread gelegentlich nicht aus dem Warten zurück.“ Beides sind Fehler beim Rendezvous, aber die Stellen, an denen man suchen muss, sind verschieden.
Im Zentrum dieses Artikels steht der Grundsatz: Die Rückkehr aus dem wait einer Bedingungsvariable garantiert nicht, dass die erwartete Bedingung gilt. Versteht man das Scheinwecken, das ohne Benachrichtigung zurückkehrt, und das gestohlene Aufwachen, bei dem die Bedingung nach der Benachrichtigung verbraucht wird, wird klar, warum while statt if nötig ist. Das verlorene Aufwachen, bei dem eine Benachrichtigung verpasst wird, behandeln wir getrennt von diesen beiden.
| Was Sie wissen oder lösen wollen | Leseabschnitt |
|---|---|
| Warum ein Warten ohne Benachrichtigung aufwacht und wie sich das vom gestohlenen Aufwachen unterscheidet | Was im Moment der Rückkehr garantiert ist, Warum die Spezifikation es zulässt |
| Korrekten Code für Win32, C++ und C# prüfen | Die Grundform je Sprache |
| Ein Timeout ist gesetzt, die Wartezeit wird trotzdem länger | Warten anhand einer Frist |
| Ein Thread kehrt trotz Benachrichtigung nicht zurück | Verlorenes Aufwachen und PulseEvent |
| Einen seltenen Absturz oder Hang untersuchen | Untersuchungsschritte nach Symptom |
Der Artikel richtet sich an Entwickler, die unter Windows Geschäftsanwendungen und Software zur Gerätesteuerung schreiben. Er prüft den Mechanismus an Primärquellen und verbindet ihn mit Umsetzungen in Win32 (C), C++ und C#.
1. Zuerst die Schlussfolgerung
Beim Wartecode sind drei Regeln einzuhalten.
| Grundsatz | Was der Code einhalten muss |
|---|---|
| Auf einen Zustand warten, nicht auf eine Benachrichtigung | Die Bedingung ist etwa „ist die Warteschlange nicht leer?“, nicht „wurde ich aufgeweckt?“ |
| Die Bedingung bei jeder Rückkehr erneut prüfen | while (!Bedingung) wait(...); in C++ die Überladung mit Prädikat wait(lock, pred) verwenden |
| Prüfung und Aktualisierung mit derselben Sperre schützen | Den Zustand aktualisieren, bevor benachrichtigt wird, damit keine Benachrichtigung in der Lücke zwischen Prüfung und Warten verloren geht |
Win32, C++ und POSIX lassen Aufwachen, die nicht an eine ausdrückliche Benachrichtigung gebunden sind, spezifikationsgemäß zu. Hinzu kommt, dass selbst bei einer Benachrichtigung ein anderer Thread die Bedingung zuerst verbrauchen kann (ein gestohlenes Aufwachen); die erneute Prüfung ist daher zwingend. Monitor.Wait in C# wird unter derselben Disziplin verwendet, mit gestohlenem Aufwachen im Blick.12345
Die andere Warnung: die vorübergehende Benachrichtigung einer Bedingungsvariable nicht mit einem Impuls auf einem Ereignis nachbauen. Insbesondere PulseEvent kann eine Benachrichtigung verpassen, und Microsoft rät, es in neuen Anwendungen nicht zu verwenden und stattdessen eine Bedingungsvariable zu nutzen.6
Der Rest des Artikels folgt dieser Reihenfolge: wie das Aufwachen funktioniert (Abschnitte 2 bis 4), die korrekte Umsetzung (Abschnitt 5), zu vermeidende Muster und Untersuchung (Abschnitte 6 und 7) und eine Checkliste (Abschnitt 8).
In der Abbildung kennzeichnet eine durchgezogene Linie eine stets geltende Beziehung und eine gestrichelte Linie eine bedingte (die Bedingungen stehen bei jeder Beziehung auf der Detailseite). Die vollständige Liste der Beziehungen (17 insgesamt, mit Beleg und Sicherheitsgrad) und die Definitionen der wichtigsten Konzepte sind auf der Detailseite der Wissenskarte (auf Japanisch) zusammengestellt. Daten: JSON-LD / Turtle
2. Was ein Scheinwecken ist — „aufgewacht“ heißt nicht „die Bedingung gilt“
2.1 Eine Bedingungsvariable gibt die Sperre frei, wartet und erwirbt sie vor der Rückkehr erneut
Eine Bedingungsvariable (condition variable) ist ein Synchronisierungsprimitiv, das einen Thread warten lässt, bis eine Bedingung gilt. Unter Win32 verwendet man die folgende Struktur und die folgenden APIs.1
| Rolle | Win32-Typ oder API |
|---|---|
| Steht für die Bedingungsvariable | CONDITION_VARIABLE |
| Gibt die Sperre frei und wartet | SleepConditionVariableCS / SleepConditionVariableSRW |
| Weckt wartende Threads | WakeConditionVariable / WakeAllConditionVariable |
Die Warte-API gibt den kritischen Abschnitt oder die SRW-Sperre, die der Thread hält, unteilbar frei und tritt in das Warten ein. Nach dem Aufwachen erwirbt sie diese Sperre erneut, bevor sie zum Aufrufer zurückkehrt.1
Dass die Sperre erneut erworben und zurückgekehrt wurde, ist jedoch etwas anderes als dass die Bedingung gilt, auf die die Anwendung gewartet hat.
2.2 Echtes Aufwachen, Scheinwecken und gestohlenes Aufwachen unterscheiden
Den Umgang mit Timeouts verschieben wir auf Abschnitt 5 und vergleichen zuerst die drei Fälle des Aufwachens.
| Fall | Verhältnis zur ausdrücklichen Benachrichtigung | Lesart im Moment der Rückkehr |
|---|---|---|
| Echtes Aufwachen | Es gibt eine Benachrichtigung | Die Bedingung gilt oft, aber die bloße Rückkehr garantiert das nicht |
| Scheinwecken | Nicht an eine ausdrückliche Benachrichtigung gebunden, die diesen Thread wecken sollte | Kehrt zurück, obwohl die Bedingung weiterhin nicht gilt |
| Gestohlenes Aufwachen (stolen wakeup) | Es gibt eine Benachrichtigung | Ein anderer Thread hat die Bedingung zuerst verbraucht, sie gilt also nicht mehr |
flowchart TB
accTitle: Drei Fälle, in denen wait zurückkehrt
accDescr: Das wait einer Bedingungsvariable kehrt nicht nur nach einer echten Benachrichtigung zurück, sondern auch nach einem Scheinwecken ohne Benachrichtigung und nach einem gestohlenen Aufwachen, bei dem eine Benachrichtigung ankam, die Bedingung aber zuerst verbraucht wurde; in jedem Fall muss die Bedingung erneut geprüft werden
w["Von wait zurückgekehrt"] --> a["Echte Benachrichtigung"]
w --> b["Scheinwecken (keine Benachrichtigung)"]
w --> c["Gestohlenes Aufwachen (Bedingung bereits verbraucht)"]
a --> r["Die Bedingung erneut prüfen, dann fortfahren"]
b --> r
c --> r
Abbildung 1: Es gibt drei Wege zurück aus wait, und der Aufrufer kann sie nicht unterscheiden, deshalb muss die Bedingung immer erneut geprüft werden.
Scheinwecken beschränkt sich nicht auf den Fall, dass im gesamten System nie eine Benachrichtigung gesendet wurde. Treffen Benachrichtigungen in kurzer Folge ein, kann die Umsetzung aus eigenen Gründen zusätzliche wartende Threads gebündelt aufwecken. Aus Sicht eines Threads, dem keine ausdrückliche Benachrichtigung entspricht, ist auch das ein Scheinwecken.
Microsoft Learn stellt fest, dass Bedingungsvariablen sowohl Scheinwecken als auch gestohlenem Aufwachen unterliegen, und verlangt, das Prädikat nach der Rückkehr aus dem Warten erneut zu prüfen, in der Regel in einer while-Schleife. Prädikat meint hier die erwartete Bedingung, etwa „die Warteschlange ist nicht leer“.1
2.3 Ob man fortfährt, entscheidet die aktuelle Bedingung, nicht der Grund des Aufwachens
Der Aufrufer kann nicht unterscheiden, welcher der drei Wege ihn zurückgebracht hat. Deshalb gilt die Form: bei jeder Rückkehr die Bedingung selbst prüfen und erneut warten, wenn sie nicht gilt.
Das while verhindert das Scheinwecken oder das gestohlene Aufwachen selbst nicht. Es ist dazu da, dass die Verarbeitung in keinem der beiden Fälle mit unerfüllter Bedingung weiterläuft. Hält man diese erneute Prüfung und den Schutz durch dieselbe Sperre aus Abschnitt 5 ein, verarbeitet der Code jeden Weg des Aufwachens korrekt.
3. Warum die Spezifikation es zulässt — genaue Benachrichtigung ist teuer
3.1 Damit nicht jede Operation den Preis strenger Benachrichtigung zahlt
Eine Umsetzung, die kein Scheinwecken erzeugt, ist theoretisch möglich. Dass POSIX, Windows und C++ es trotzdem zulassen, liegt an der Leistung und an einem Entwurf, in dem die Warteseite die Bedingung erneut prüft. Auch die Rationale von pthread_cond_wait unter POSIX erklärt diese Entscheidung.3
Eine Benachrichtigung, die „zuverlässig genau einen Thread weckt“, streng umzusetzen, legt besonders auf Multiprozessoren zusätzliche Synchronisierungskosten auf jede Operation der Bedingungsvariable. Zwischen Benachrichtigung und Aufwachen sitzt der Scheduler, und es gibt Unterbrechungen und Preemption. Ein seltenes zusätzliches Aufwachen zuzulassen und auf der Warteseite erneut zu prüfen hält die Umsetzung schneller.3
Die erneute Prüfung in der Schleife hat einen weiteren Vorteil: sie macht die Absicht im Code sichtbar und macht den Code robuster. Behandelt man eine Benachrichtigung nicht als „Garantie, dass die Bedingung gilt“, sondern als Hinweis, dass sich die Bedingung geändert haben könnte, trägt der Code auch Änderungen auf der benachrichtigenden Seite wie zusätzliches oder gebündeltes Aufwecken.3
3.2 Auch ohne Scheinwecken bleibt das gestohlene Aufwachen
Ein gestohlenes Aufwachen entsteht in der Zeitlücke zwischen der Benachrichtigung und dem erneuten Erwerb der Sperre. Selbst wenn der Produzent ein Element in die Warteschlange legt und Verbraucher A weckt, ist die Warteschlange leer, sobald A zurückkehrt, wenn Verbraucher B dieses eine Element nimmt, bevor A die Sperre erneut erwirbt.
sequenceDiagram
accTitle: Zeitablauf eines gestohlenen Aufwachens
accDescr: Der Produzent legt ein Element in die Warteschlange und weckt den wartenden Verbraucher A, aber bevor A die Sperre erneut erwirbt, erwirbt Verbraucher B die Sperre und nimmt das eine Element, sodass die Warteschlange leer ist, wenn A aufwacht
participant A as Verbraucher A (wartend)
participant P as Produzent
participant B as Verbraucher B
P->>P: Ein Element in die Warteschlange legen
P->>A: WakeConditionVariable
Note over A: Aufgewacht, wartet auf den erneuten Erwerb der Sperre
B->>B: Die Sperre erwerben und ein Element entnehmen
A->>A: Die Sperre erneut erwerben und aus wait zurückkehren
Note over A: Die Warteschlange ist leer (gestohlen)
A->>A: In while erneut prüfen und wieder warten
Abbildung 2: Ein gestohlenes Aufwachen, bei dem ein dritter Thread die Bedingung in der Zeitlücke zwischen Benachrichtigung und Aufwachen verbraucht, kann in jeder Umsetzung auftreten.
Solange ein dritter Thread die Sperre zuerst nehmen kann, lässt sich dieses gestohlene Aufwachen durch bloßes Polieren der Umsetzung nicht beseitigen. Selbst wenn Scheinwecken vollständig entfielen, wäre die Schleife auf der Warteseite nötig, solange gestohlenes Aufwachen existiert. Unter dieser Voraussetzung lautet die Entwurfsentscheidung, Scheinwecken zuzulassen und Bedingungsvariablen schnell zu halten.
4. In welcher Schicht es unter Windows auftritt
4.1 Gemeinsam ist die Disziplin der erneuten Prüfung; die Gründe des Aufwachens unterscheidet man
| API oder Bibliothek | Warum die erneute Prüfung nötig ist |
|---|---|
| Win32-Bedingungsvariablen | Scheinwecken und gestohlenes Aufwachen sind ausdrücklich dokumentiert2 |
WaitOnAddress |
Darf auch aus anderen Gründen als einem Signal an die angegebene Adresse vorzeitig zurückkehren7 |
C++ std::condition_variable |
Ein wait ohne Prädikat kann scheinbar aufwachen48 |
.NET Monitor.Wait |
Ein anderer Thread kann die Bedingung zwischen dem Aufwachen und dem erneuten Erwerb der Sperre verbrauchen5 |
Auch das offizielle Win32-Beispiel einer Produzenten-Verbraucher-Warteschlange schreibt das Warten in einer while-Schleife.9
WaitOnAddress, verfügbar ab Windows 8, ist eine niedrigere API, die darauf wartet, dass sich der Wert an einer angegebenen Adresse ändert. Als Beispiele für eine vorzeitige Rückkehr ohne Signal nennt die offizielle Dokumentation einen Zustand mit wenig Speicher, das Aufgeben eines früheren Wake für dieselbe Adresse und die Ausführung auf einem Checked Build. Auch das Verwendungsbeispiel ist eine while-Schleife, die den Wert erneut vergleicht.7
4.2 Das wait mit Prädikat in C++ übernimmt die Schleife
Die MSVC-Dokumentation erklärt, dass wait(lock, pred) im Kern Folgendes ausführt.4
while (!Pred())
wait(Lck);
Auch cppreference hält ausdrücklich fest, dass ein wait ohne Prädikat scheinbar zurückkehren kann. In der Form mit Prädikat kann man diese Schleife der erneuten Prüfung der Bibliothek überlassen.8
4.3 In C# stehen gestohlenes Aufwachen und der erneute Erwerb der Sperre im Vordergrund
Monitor.Wait / Pulse verwenden eine Warteschlange und eine Bereitschaftswarteschlange. Ein durch Pulse / PulseAll aufgeweckter Thread wechselt in die Bereitschaftswarteschlange und verlässt Wait erst, nachdem er die Sperre erneut erworben hat. Dass in diesem Intervall ein anderer Thread die Bedingung zuerst verbrauchen kann, ist dasselbe wie unter Win32.510
Bei Monitor.Wait in .NET sollte man nicht ein grundloses Aufwachen der Art von Bedingungsvariablen unterstellen, sondern getrennt denken: dieselbe while-Disziplin ist wegen gestohlenem Aufwachen und Timeouts nötig. Auch die Dokumentation geht von einer Verwendung aus, in der der Thread die Bedingung, wegen der er gewartet hat, erneut auswertet und Wait bei Bedarf erneut aufruft.5
flowchart TB
accTitle: Jede Schicht verlangt die erneute Prüfung des Prädikats
accDescr: Auf jeder Schicht, C++ std::condition_variable, .NET Monitor, Win32 CONDITION_VARIABLE und dem niedrigeren WaitOnAddress, verlangt die offizielle Dokumentation, die Bedingung nach dem Aufwachen erneut zu prüfen
cpp["C++ std::condition_variable"] --> rule["Nach dem Aufwachen die Bedingung erneut prüfen (while)"]
net[".NET Monitor.Wait"] --> rule
win["Win32 CONDITION_VARIABLE"] --> rule
woa["WaitOnAddress"] --> rule
Abbildung 3: Unabhängig von Sprache oder Framework verlangt jede Schicht des Warteprimitivs offiziell eine erneute Prüfung nach dem Aufwachen.
5. Die richtige Art zu warten — mit while und Prädikat schreiben
Die gemeinsame Form: die Bedingung prüfen und nur fortfahren, wenn sie gilt
Worauf man wartet, ist nicht „ob eine Benachrichtigung empfangen wurde“, sondern ein durch eine Sperre geschützter gemeinsam genutzter Zustand. Die Anzahl in der Warteschlange oder ein Flag im Inneren derselben Sperre prüfen und aktualisieren, bei Nichterfüllung wait aufrufen und bei der Rückkehr erneut prüfen.
flowchart TB
accTitle: Ablauf einer korrekten Warteschleife
accDescr: Die Sperre erwerben und die Bedingung prüfen; gilt sie nicht, die Sperre freigeben und schlafen; nach dem Aufwachen die Sperre erneut erwerben und zur Bedingungsprüfung zurückkehren. Nur wenn die Bedingung gilt, mit gehaltener Sperre fortfahren
l["Die Sperre erwerben"] --> c{"Gilt die Bedingung?"}
c -->|"Nein"| s["wait (Sperre freigeben und schlafen)"]
s --> wk["Aufwachen (Sperre erneut erwerben)"]
wk --> c
c -->|"Ja"| go["Mit gehaltener Sperre verarbeiten"]
Abbildung 4: Korrektes Warten ist eine Schleife, und zwischen Prüfung der Bedingung und Verarbeitung gibt es keine Lücke (beides geschieht bei gehaltener Sperre).
Mit dieser Form hat man im Moment des Verlassens der Schleife bestätigt, dass die Bedingung gilt, während die Sperre noch gehalten wird. Man verbraucht den Zustand so, und zwischen Prüfung und Verarbeitung entsteht keine Lücke, in der ein anderer Thread dazwischenfahren kann.
Die Grundform unter Win32 (C)
CRITICAL_SECTION cs;
CONDITION_VARIABLE cv;
int queueCount = 0; // gemeinsam genutzter Zustand, geschützt durch cs
// Einmalig beim Start initialisieren (bei statischer Initialisierung cv = CONDITION_VARIABLE_INIT)
InitializeCriticalSection(&cs);
InitializeConditionVariable(&cv);
// Warteseite (Verbraucher)
EnterCriticalSection(&cs);
while (queueCount == 0) { // immer while, niemals if
SleepConditionVariableCS(&cv, &cs, INFINITE);
}
// Hier wird die Sperre gehalten und queueCount > 0 ist garantiert
--queueCount;
LeaveCriticalSection(&cs);
// Benachrichtigende Seite (Produzent)
EnterCriticalSection(&cs);
++queueCount; // den Zustand innerhalb der Sperre aktualisieren
LeaveCriticalSection(&cs);
WakeConditionVariable(&cv); // Benachrichtigung nach Freigabe der Sperre ist in Ordnung
Zu unterscheiden sind die Aktualisierung des Zustands und die Stelle, an der die Benachrichtigung aufgerufen wird. ++queueCount muss immer innerhalb der Sperre erfolgen. WakeConditionVariable dagegen kann von innerhalb oder außerhalb der Sperre aufgerufen werden. Microsoft sagt, es sei in der Regel besser, nach Freigabe der Sperre zu wecken, um Kontextwechsel zu verringern.1
Die Grundform in C++ — das wait mit Prädikat als Vorgabe
std::mutex m;
std::condition_variable cv;
std::queue<Item> q;
// Warteseite
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&] { return !q.empty(); }); // intern while(!pred) wait
Item item = std::move(q.front());
q.pop();
lk.unlock();
// Benachrichtigende Seite
{
std::lock_guard<std::mutex> lk(m);
q.push(std::move(item));
}
cv.notify_one();
Neuer Code sollte das wait mit Prädikat als Vorgabe nehmen. Ein vorhandenes while (q.empty()) cv.wait(lk); ist ebenfalls eine korrekte Form, deshalb muss man es nicht allein deshalb umschreiben, weil die Schleife von Hand geschrieben ist. Das Problem ist if (q.empty()) cv.wait(lk);, das nicht erneut prüft.
Die Form mit Prädikat übernimmt nur die Schleife. Die Disziplin, den gemeinsam genutzten Zustand, den das Prädikat liest, auch auf der benachrichtigenden Seite mit demselben Mutex zu schützen und den Zustand vor der Benachrichtigung zu aktualisieren, bleibt nötig.
Die Grundform in C#
private readonly object _gate = new();
private readonly Queue<Item> _queue = new();
// Warteseite
lock (_gate)
{
while (_queue.Count == 0) // immer while, niemals if
{
Monitor.Wait(_gate);
}
var item = _queue.Dequeue();
}
// Benachrichtigende Seite
lock (_gate)
{
_queue.Enqueue(item);
Monitor.Pulse(_gate); // Pulse von Monitor darf nur innerhalb der Sperre aufgerufen werden
}
Monitor.Wait / Pulse / PulseAll werden innerhalb eines synchronisierten Blocks aufgerufen, der die betreffende Sperre hält. Außerhalb der Sperre werfen sie SynchronizationLockException. Das darf man nicht mit Win32 verwechseln, wo die Benachrichtigung nach außerhalb der Sperre verlegt werden kann.10
Warten mit Timeout — die Restzeit aus einer Frist berechnen
Übergibt man jedes Mal denselben Timeout-Wert, verlängert sich die Wartezeit bei jeder Rückkehr durch ein Scheinwecken. Die Form lautet: zuerst die Frist festlegen und bei jeder Rückkehr die Restzeit berechnen.
ULONGLONG deadline = GetTickCount64() + timeoutMs;
EnterCriticalSection(&cs);
while (queueCount == 0) {
ULONGLONG now = GetTickCount64();
if (now >= deadline) {
break; // Timeout (Bedingung weiterhin unerfüllt)
}
if (!SleepConditionVariableCS(&cv, &cs, (DWORD)(deadline - now)) &&
GetLastError() != ERROR_TIMEOUT) {
break; // bei einem anderen Fehler als Timeout das Warten beenden und verlassen
}
// ERROR_TIMEOUT erhält die endgültige Prüfung in der while-Bedingung und der Fristprüfung
}
BOOL ready = (queueCount > 0);
if (ready) { --queueCount; }
LeaveCriticalSection(&cs);
flowchart TB
accTitle: Korrekter Ablauf eines Wartens mit Timeout
accDescr: Zuerst die Frist festlegen und bei jedem Aufwachen Bedingung und Frist prüfen; ist die Frist noch nicht erreicht, die Restzeit neu berechnen und zum Warten zurückkehren
d["Die Frist festlegen"] --> c{"Gilt die Bedingung?"}
c -->|"Ja"| go["Zur Verarbeitung fortfahren"]
c -->|"Nein"| t{"Ist die Frist abgelaufen?"}
t -->|"Ja"| to["Den Timeout behandeln"]
t -->|"Nein"| w["Die Restzeit berechnen und warten"]
w --> c
Abbildung 5: Ein Warten mit Timeout übergibt nicht erneut „dieselbe Wartezeit“, sondern berechnet die Restzeit aus der Frist.
In C++ kann man diese Verarbeitung der Überladung mit Prädikat von wait_until überlassen, die einen absoluten Zeitpunkt entgegennimmt. Weil sie auch beim Timeout den Endwert des Prädikats zurückgibt, kann man anhand dessen entscheiden, ob die Bedingung am Ende galt.4
6. Muster, die man vermeiden sollte
6.1 Die Bedingung nur einmal mit if prüfen
Tritt ein Scheinwecken oder ein gestohlenes Aufwachen ein, läuft die Verarbeitung mit unerfüllter Bedingung weiter. Das zeigt sich als seltener Absturz oder Datenbeschädigung: Entnahme aus einer leeren Warteschlange, Lesen uninitialisierter Daten, doppelte Freigabe. Auf das while oder das wait mit Prädikat aus Abschnitt 5 umstellen.
6.2 Prüfung oder Aktualisierung der Bedingung außerhalb der Sperre
Hier geht es nicht um „zu oft aufwachen“, sondern um ein verlorenes Aufwachen (lost wakeup): die Benachrichtigung verpassen und für immer schlafen.
Sieht die Warteseite die Bedingung außerhalb der Sperre, entscheidet „noch nicht“ und aktualisiert die benachrichtigende Seite den Zustand und benachrichtigt, bevor die Warteseite in das Warten eintritt, gibt es in diesem Augenblick keinen Wartenden. Die Warteseite tritt nach dem Verschwinden der Benachrichtigung in das Warten ein und wartet weiter, wenn keine weitere Benachrichtigung kommt.
sequenceDiagram
accTitle: Zeitablauf eines verlorenen Aufwachens
accDescr: Prüft die Warteseite die Bedingung außerhalb der Sperre und aktualisiert die benachrichtigende Seite den Zustand und benachrichtigt in der Lücke vor dem Eintritt ins Warten, wird die Benachrichtigung an eine Bedingungsvariable ohne Wartenden gesendet und verschwindet, und die Warteseite wartet weiter auf eine Benachrichtigung, die nie kommt
participant W as Warteseite
participant N as Benachrichtigende Seite
W->>W: Die Bedingung außerhalb der Sperre prüfen (gilt nicht)
N->>N: Den Zustand aktualisieren und benachrichtigen
Note over N: In diesem Augenblick kein Wartender
W->>W: Ins Warten eintreten
Note over W: Die Benachrichtigung ist bereits weg, daher kein Aufwachen
Abbildung 6: Die Prüfung der Bedingung außerhalb der Sperre lässt eine Benachrichtigung durch die Lücke zwischen Prüfung und wait hindurch: ein verlorenes Aufwachen.
Dass die Warte-API einer Bedingungsvariable das Freigeben der Sperre und den Eintritt ins Warten unteilbar macht, dient dazu, diese Lücke zu schließen. Hält man die Disziplin ein, Prüfung und Aktualisierung der Bedingung mit derselben Sperre zu schützen und die Warte-API bei gehaltener Sperre zu betreten, verhindert man dieses verlorene Aufwachen.1
6.3 Eine vorübergehende Benachrichtigung mit einem Impuls auf einem Ereignis nachbauen
CreateEvent und SetEvent zu verwenden ist an sich kein Anti-Pattern. Ereignisse haben berechtigte Einsatzstellen wie die folgenden.
| Zweck | Wann ein Ereignis verwendet wird |
|---|---|
| Einen einzelnen Verbraucher wecken | Ein Aufbau, in dem der aufgeweckte Verbraucher die Warteschlange bis zur Leere verarbeitet |
| Einen Stopp signalisieren | Eine Stoppanweisung, die einmal gesetzt nie wieder gesenkt wird, als manuell rücksetzbares Ereignis |
| Zusammen mit anderen Wartezielen warten | Aufnahme in WaitForMultipleObjects |
| Rendezvous über Prozessgrenzen | Eine Verwendung, die eine Bedingungsvariable, die nicht zwischen Prozessen geteilt werden kann, nicht abdecken kann |
Eine Bedingungsvariable ist ein Objekt im Benutzermodus, das nicht zwischen Prozessen geteilt werden kann. Je nach Verwendung ist der Ersatz durch eine Bedingungsvariable selbst nicht möglich.1
Gefährlich ist der Versuch, mit einem Ereignis das Verhalten einer Bedingungsvariable nachzubauen, „nur die in diesem Augenblick wartenden Threads zu wecken und keinen Benachrichtigungszustand zurückzulassen“. Diese Idee führt zum Problem von PulseEvent im nächsten Abschnitt.
6.4 PulseEvent verwenden
PulseEvent ist eine API, die bei einem manuell rücksetzbaren Ereignis die in diesem Augenblick wartenden Threads weckt und das Ereignis sofort in den nicht signalisierten Zustand zurücksetzt. Microsoft stellt jedoch ausdrücklich fest, dass sie unzuverlässig ist und vor allem der Abwärtskompatibilität dient, neue Anwendungen sie also nicht verwenden und stattdessen eine Bedingungsvariable nutzen sollen.6
Der Grund: ein wartender Thread kann durch einen Kernelmodus-APC vorübergehend aus dem Wartezustand genommen werden und nach Abschluss des APC ins Warten zurückkehren. Wird PulseEvent in diesem Intervall aufgerufen, gehört der Thread nicht zu „den im Aufrufaugenblick Wartenden“ und wird nicht geweckt. Kernel-APCs sind internes Verhalten des Betriebssystems, das die Anwendung nicht steuern kann.611
Dieses Problem ist auch die statische Analysewarnung C28648.12 Ein Scheinwecken ist das Problem „zu oft aufwachen“; hier geht es um „nicht aufwachen, obwohl es nötig wäre“. Weil die Benachrichtigung selbst verloren geht, rettet das bloße Einwickeln des Wartens in while nicht.
6.5 Ohne gehaltene Sperre benachrichtigen, bevor der Zustand aktualisiert wird
Benachrichtigt man zuerst ohne gehaltene Sperre und erwirbt danach die Sperre und aktualisiert den Zustand, kann die aufgeweckte Seite den alten Zustand sehen und wieder einschlafen. Folgt der Aktualisierung keine Benachrichtigung, bleibt sie dort.
Die folgenden zwei Fälle unterscheidet man jedoch.
| Reihenfolge | Ergebnis |
|---|---|
| Außerhalb der Sperre benachrichtigen, danach die Sperre erwerben und den Zustand aktualisieren | Es gibt eine Lücke, in der die aufgeweckte Seite vor der Aktualisierung erneut prüft und schläft |
| Benachrichtigen, aktualisieren, dann freigeben, alles bei gehaltener derselben Sperre | Die Warteseite kann erst nach erneutem Erwerb der Sperre prüfen, deshalb entsteht durch diese Reihenfolge kein tatsächlicher Schaden |
Statt die Sicherheit des letzteren jedes Mal prüfen zu müssen, ist es klarer und sicherer, sich auf die Reihenfolge den Zustand innerhalb derselben Sperre aktualisieren, danach benachrichtigen festzulegen.
7. Wie man untersucht, wenn man darauf trifft
7.1 „Läuft trotz unerfüllter Bedingung weiter“ von „wacht nicht auf“ trennen
| Symptom | Was man zuerst prüft | Untersuchungsmethode |
|---|---|---|
| Entnahme aus einer leeren Warteschlange, Absturz, fehlende Ergebnisse | Ob die Bedingung nach dem Warten erneut geprüft wird | Um cv.wait( ohne Prädikat, SleepConditionVariableCS und Monitor.Wait herum suchen |
| Ein Thread, der aufwachen sollte, kehrt nicht zurück, der Prozess hängt | Ob ein verlorenes Aufwachen oder PulseEvent vorliegt |
In einem Dump den Stapel jedes Threads prüfen und von der steckengebliebenen Warte-API zur benachrichtigenden Seite zurückverfolgen |
Ein cv.wait(lk) ohne Prädikat zu finden ist kein Problem, wenn es in einem korrekten while steht. Kandidaten per Suche sammeln und prüfen, ob die Umgebung nur ein if ist und ob Prüfung und Aktualisierung der Bedingung unter derselben Sperre erfolgen. Das sind Stellen, die man ohne Reproduktion prüfen kann.
Bei einem Hang die Wartestelle aus einem Dump bestimmen und im Code nachverfolgen, wer in welcher Reihenfolge benachrichtigen sollte. Prüfung der Bedingung außerhalb der Sperre, Benachrichtigung außerhalb der Sperre vor der Zustandsaktualisierung und PulseEvent prüfen.
flowchart TB
accTitle: Ablauf der Trennung anhand des Symptoms
accDescr: Ist das Symptom, dass die Verarbeitung mit unerfüllter Bedingung weiterläuft, im Code nach Warten ohne Prädikat suchen; ist das Symptom ein Thread, der nicht aufwacht, die Wartestelle aus einem Dump bestimmen und ein verlorenes Aufwachen oder PulseEvent vermuten
s["Ein Fehler, der nur selten auftritt"] --> a["Die Verarbeitung läuft mit unerfüllter Bedingung weiter"]
s --> b["Ein Thread, der aufwachen sollte, wacht nicht auf"]
a --> a1["Im Code nach Warten ohne Prädikat suchen"]
b --> b1["Die wartenden Threads aus einem Dump bestimmen"]
a1 -.-> a2["if durch while oder wait mit Prädikat ersetzen"]
b1 -.-> b2["Verlorenes Aufwachen und PulseEvent vermuten"]
Abbildung 7: Ob das Symptom „zu weit fortfahren“ oder „nie aufwachen“ ist, entscheidet sowohl, wo man sucht, als auch, womit man untersucht.
7.2 Vorher und nachher unter denselben Stressbedingungen vergleichen
Um einen seltenen Fehler zu reproduzieren, verbreitert man das Wettlauffenster und erhöht die Streuung der Zeitpunkte. Methoden sind, mehr Threads als physische Kerne zu verwenden, ein diagnostisches Sleep zwischen Warten und Benachrichtigung zu setzen und Debug- wie Release-Builds zu versuchen.
Auch wenn man vergleicht, um zu schließen „nach der Korrektur des Wartens trat es nicht mehr auf“, denselben Stress vor und nach der Korrektur anlegen.
8. Zusammenfassung — Checkliste
Eine Benachrichtigung ist ein Hinweis, dass sich die Bedingung geändert haben könnte; die Grundlage, fortzufahren, ist die Bedingung selbst, geprüft innerhalb der Sperre.
| Prüfpunkt | Korrekte Form |
|---|---|
| Nach der Rückkehr aus dem Warten | Echtes Aufwachen, Scheinwecken und gestohlenes Aufwachen nicht unterscheiden wollen; die Bedingung erneut prüfen |
| Schreibweise des Wartens | In while einwickeln. Neuer C++-Code nimmt das wait(lock, pred) mit Prädikat als Vorgabe |
| Gemeinsam genutzter Zustand | Prüfung und Aktualisierung mit derselben Sperre schützen und den Zustand vor der Benachrichtigung aktualisieren |
| Stelle des Benachrichtigungsaufrufs | Win32/C++ dürfen nach Freigabe der Sperre benachrichtigen. Pulse in C# wird innerhalb der Sperre aufgerufen |
| Timeout | Die Restzeit aus einer Frist berechnen. In C++ wait_until mit Prädikat verwenden |
| Verwendung von Ereignissen | Berechtigte Verwendungen von vorübergehenden Impulsen unterscheiden und nicht von PulseEvent abhängen |
Scheinwecken ist eine Spezifikation, die Win32, C++ und POSIX bewusst zugelassen haben. Die Antwort ist Disziplin auf der Warteseite, nicht das Warten auf eine Korrektur des Betriebssystems oder ein Wechsel der Bibliothek. Monitor.Wait in .NET wird von grundlosem Aufwachen unterschieden, führt aber dieselbe erneute Prüfung aus, um gestohlenes Aufwachen und Timeouts aufzufangen.
Bei einem Absturz bei „der Stelle, die trotz unerfüllter Bedingung fortfährt“ beginnen; bei einem Hang bei „der Stelle, die eine Benachrichtigung verpasst“. Diese Trennung und die Checkliste auch beim Review vorhandenen Codes verwenden.
Verwandte Artikel
- Praktische Multithreading-Best-Practices: C++-Edition
- Praktische Multithreading-Best-Practices: C-Edition
- Praktische Multithreading-Best-Practices: .NET-Edition
- Warum Sie unter Windows Ereigniswarten gegenüber Sleep(1) bevorzugen sollten
- Fallstricke bei Shared Memory und Best Practices für die Praxis
- Die Tiefen von Windows I/O (Teil 2) — Synchrones und asynchrones I/O: Was OVERLAPPED wirklich bedeutet
Verwandte Beratungsleistungen
Die KomuraSoft LLC übernimmt Design-Reviews von Multithread-Code, die Ursachenuntersuchung (Dump-Analyse) von Abstürzen und Hangs, die „sich nur gelegentlich reproduzieren“, und die Umstellung älteren Synchronisierungscodes (Abhängigkeit von Ereignissen, PulseEvent und ähnlichem) auf eine Grundlage mit Bedingungsvariablen. Der Einstieg bei der Eingrenzung des Symptoms ist in Ordnung; melden Sie sich gern.
- Technische Beratung und Design-Review
- Fehleruntersuchung und Ursachenanalyse
- Windows-App-Entwicklung
- Kontakt
Quellen
-
Microsoft Learn, Condition Variables. Dazu, dass eine Bedingungsvariable ein Objekt im Benutzermodus ist, das das Freigeben der Sperre und den Eintritt ins Warten unteilbar ausführt; dazu, dass es Scheinwecken (Aufwachen, die nicht an ein ausdrückliches Wecken gebunden sind) und gestohlenes Aufwachen (ein anderer Thread läuft vor dem aufgeweckten Thread) gibt, weshalb das Prädikat nach der Rückkehr aus dem Warten in einer while-Schleife erneut geprüft werden sollte; und dazu, dass die Benachrichtigung von innerhalb oder außerhalb der Sperre möglich ist, das Wecken nach Freigabe der Sperre aber besser ist, um Kontextwechsel zu verringern. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, SleepConditionVariableCS function (synchapi.h). Dazu, den angegebenen kritischen Abschnitt unteilbar freizugeben und auf der Bedingungsvariable zu warten; dazu, dass der aufgeweckte Thread den kritischen Abschnitt erneut erwirbt, bevor er zurückkehrt; dazu, dass bei Timeout ERROR_TIMEOUT zurückgegeben wird; und dazu, dass es Scheinwecken und gestohlenes Aufwachen gibt, weshalb das Prädikat nach der Rückkehr aus dem Warten (in der Regel in einer while-Schleife) erneut geprüft werden sollte. ↩ ↩2
-
The Open Group Base Specifications, pthread_cond_timedwait, pthread_cond_wait. Dazu, dass Scheinwecken aus pthread_cond_wait / pthread_cond_timedwait auftreten können; dazu, dass die Rückkehr aus wait nichts über den Wert des Prädikats bedeutet, weshalb das Prädikat erneut ausgewertet werden sollte; und dazu, dass die Rationale festhält, eine Umsetzung, die „genau einen weckt“, Operationen der Bedingungsvariable besonders auf Multiprozessoren verlangsamen kann, und dass die Zulassung von Scheinwecken eine Schleife zur Prüfung des Prädikats erzwingt und Anwendungen robuster macht. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, condition_variable Class. Dazu, dass wait ohne Prädikat durch notify_one / notify_all gelöst wird und außerdem scheinbar aufwachen kann; dazu, dass wait(lock, pred) mit Prädikat im Kern while (!Pred()) wait(Lck); ausführt; und dazu, dass wait_for / wait_until dieselbe Eigenschaft und Überladungen mit Prädikat haben. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Monitor.Wait Method. Dazu, dass Wait die Sperre freigibt und in die Warteschlange eintritt; dazu, dass nach dem Aufwecken durch Pulse / PulseAll erst nach erneutem Erwerb der Sperre zurückgekehrt wird; und dazu, dass die vorgesehene Verwendung darin besteht, dass der aufgeweckte Thread die Bedingung, wegen der er gewartet hat, erneut auswertet und Wait bei Bedarf erneut aufruft. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, PulseEvent function (winbase.h). Dazu, dass ein wartender Thread durch einen Kernelmodus-APC vorübergehend aus dem Wartezustand genommen werden und nach Abschluss des APC zurückkehren kann, sodass der Thread nicht freigegeben wird, wenn PulseEvent in diesem Intervall aufgerufen wird; und dazu, dass PulseEvent deshalb unzuverlässig ist, in neuen Anwendungen nicht verwendet und durch eine Bedingungsvariable ersetzt werden sollte. ↩ ↩2 ↩3
-
Microsoft Learn, WaitOnAddress function (synchapi.h). Dazu, dass die Funktion, die auf die Änderung des Werts an einer Adresse wartet, bei einem Signal zur Rückkehr garantiert ist, aber auch aus anderen Gründen zurückkehren darf; dazu, dass als Beispiele für frühes Aufwachen ein Zustand mit wenig Speicher, das Aufgeben eines früheren Wake für dieselbe Adresse und die Ausführung auf einem Checked Build genannt werden; und dazu, dass der Wert nach der Rückkehr deshalb erneut verglichen werden sollte, wobei das offizielle Beispiel selbst eine while-Schleife ist. ↩ ↩2
-
cppreference.com, std::condition_variable::wait. Dazu, dass wait ohne Prädikat durch ein Scheinwecken gelöst werden kann; und dazu, dass die Überladung mit Prädikat zu while (!pred()) wait(lock); äquivalent ist und als Schleife definiert ist, die bei jeder Benachrichtigung oder jedem Scheinwecken die Sperre erneut erwirbt und das Prädikat prüft. ↩ ↩2
-
Microsoft Learn, Using Condition Variables. Zum offiziellen Beispiel, das eine Produzenten-Verbraucher-Warteschlange mit einem kritischen Abschnitt und zwei Bedingungsvariablen (BufferNotEmpty und BufferNotFull) umsetzt. Das Warten erfolgt innerhalb einer Schleife, die das Prädikat prüft. ↩
-
Microsoft Learn, Monitor.PulseAll Method. Dazu, dass PulseAll die Threads der Warteschlange in die Bereitschaftswarteschlange verschiebt und der nächste Thread der Bereitschaftswarteschlange die Sperre erwirbt, sobald sie freigegeben wird; und dazu, dass Pulse / PulseAll / Wait nur aus einem synchronisierten Block heraus aufgerufen werden können. ↩ ↩2
-
Microsoft Learn, Waits and APCs. Dazu, dass Kernel-APCs präemptiv ausgeführt werden und das System das Warten intern unterbricht und fortsetzt, ohne aus der Warte-API zurückzukehren, sodass ein vorübergehendes Signal wie KePulseEvent in diesem Intervall verpasst werden kann. ↩
-
Microsoft Learn, C28648: PulseEvent is an unreliable function. Dazu, dass die statische Analyse die Verwendung von PulseEvent warnt; dazu, dass ein Thread, der wegen eines APC aus dem Warten war, nicht freigegeben wird und dauerhaft hängen kann; und zur Leitlinie, es durch SetEvent oder ein anderes Synchronisierungsobjekt zu ersetzen. ↩
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
Warum Argumente zerbrechen — Die Regeln der Windows-Kommandozeilenargumente
Windows übergibt CreateProcess eine einzige Zeichenfolge, die der Empfänger zerlegt. Behandelt die Regeln von CommandLineToArgvW, CRT und...
DllMain und die Ladersperre — Der wahre Grund, warum man sagt, in der DLL-Initialisierung nichts zu tun
Warum Sie aus DllMain weder LoadLibrary aufrufen noch mit Threads synchronisieren dürfen. Anhand von Primärquellen erklärt dieser Artikel...
Praktische Multithreading-Best-Practices: C-Edition — sicher schreiben nach der Win32-API
Multithreading in C unter Win32 folgt etablierten Mustern: Threads mit _beginthreadex erzeugen, SRW-Locks und Bedingungsvariablen, Interl...
Praktische Multithreading-Best-Practices: C++-Edition — Fehler mit RAII und jthread strukturell ausschließen
In C++ wird eine Datenrace zu undefiniertem Verhalten. Der Artikel ordnet die Falle des std::thread-Destruktors, das Stoppen mit jthread ...
Praktische Best Practices für Multithreading: .NET-Edition — Was Sie entscheiden sollten, bevor Sie weitere Threads hinzufügen
Eine praxisnahe Übersicht der Entwurfsregeln, die verhindern, dass Multithreading-Code in .NET/C# gelegentlich abstürzt oder hängen bleib...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
Fehleranalyse und Langzeitprobleme
Sporadische Fehler, Kommunikationsdiagnose, Langzeitabstürze und Tests von Fehlerpfaden.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Ist ein Scheinwecken ein Fehler im Betriebssystem oder in der Bibliothek?
- Es ist kein Fehler, sondern ein in der Spezifikation festgeschriebenes Verhalten. Sowohl SleepConditionVariableCS unter Win32 als auch std::condition_variable in C++ und pthread_cond_wait unter POSIX halten in der offiziellen Dokumentation bzw. im Standard ausdrücklich fest, dass ein Aufwachen auftreten kann, das nicht an eine Benachrichtigung gebunden ist. Eine Umsetzung, die das verbietet, ist theoretisch möglich, würde aber jede Operation der Bedingungsvariable verlangsamen (besonders die Benachrichtigung auf Multiprozessoren). Deshalb wird das Verhalten mit der Abwägung zugelassen, dass die Korrektheit erhalten bleibt, wenn die Warteseite die Bedingung erneut prüft. Die Abhilfe besteht also nicht darin, auf eine Korrektur des Betriebssystems zu warten, sondern wait immer in einer while-Schleife (oder als wait mit Prädikat) zu schreiben.
- Verschlechtert sich die Leistung, wenn man wait in while einwickelt?
- In der Praxis ist der Aufwand vernachlässigbar. Was die while-Schleife hinzufügt, ist nur eine Prüfung der Bedingung bei jedem Aufwachen, und das ist ein leichter Vergleich bei gehaltener Sperre. Scheinwecken selbst sind selten, deshalb läuft die zusätzliche Schleife nur in Ausnahmefällen. Der Preis, if stehen zu lassen, ist dagegen ein nur selten reproduzierbarer Fehler, bei dem die Verarbeitung mit unerfüllter Bedingung weiterläuft; das ist kein Vergleich. Was die Wartekosten einer Bedingungsvariable wirklich bestimmt, ist die Konkurrenz um die Sperre und die Häufigkeit der Benachrichtigungen, nicht das Vorhandensein von while.
- Kann ich Scheinwecken ignorieren, wenn ich das wait mit Prädikat in C++ nutze?
- Für die Warteschleife ja: cv.wait(lock, pred) führt im Kern while (!pred()) wait(lock); aus, sodass Scheinwecken und gestohlene Aufwachen automatisch aufgenommen werden. Neuer C++-Code sollte die Überladung mit Prädikat als Vorgabe nehmen. Die Aktualisierung des gemeinsam genutzten Zustands, den das Prädikat liest, muss trotzdem mit demselben Mutex geschützt werden, und die benachrichtigende Seite muss den Zustand aktualisieren, bevor sie notify aufruft. Das wait mit Prädikat übernimmt nur die Schleife, nicht die Disziplin der Sperre.
- Tritt dasselbe Problem auch bei Monitor.Wait in C# auf?
- Ja. Ein Thread, der in Monitor.Wait wartet, wird durch Pulse/PulseAll aufgeweckt und erwirbt die Sperre erneut, bevor er Wait verlässt. In diesem Intervall kann ein anderer Thread die Sperre zuerst erwerben und die Bedingung verbrauchen (ein gestohlenes Aufwachen). Auch die Dokumentation von Microsoft geht davon aus, dass der aufgeweckte Thread die Bedingung, wegen der er gewartet hat, erneut auswertet und Wait bei Bedarf erneut aufruft. Die Grundform in C# ist deshalb ebenfalls while (!Bedingung) Monitor.Wait(gate);. Dass Wait/Pulse nur innerhalb einer lock-Anweisung aufgerufen werden dürfen, ist eine Einschränkung, die sich von Win32 unterscheidet.
- Gibt es Scheinwecken auch, wenn man mit WaitForSingleObject auf ein Ereignis wartet?
- Bei einem gewöhnlichen (nicht alertable) Warten wird WAIT_OBJECT_0 nur zurückgegeben, wenn das Objekt tatsächlich signalisiert ist. Ein grundloses Aufwachen der Art, die Bedingungsvariablen haben, tritt dann nicht auf. Allerdings sind „das Ereignis ist signalisiert“ und „die Bedingung der Anwendung gilt“ verschiedene Dinge. In einem Entwurf, in dem mehrere Verbraucher durch dasselbe Ereignis aufgeweckt werden, verbraucht der Thread, der die Sperre zuerst nimmt, die Bedingung; nach dem Aufwachen ist die Prüfung der Bedingung also trotzdem nötig. Ein Entwurf, der die vorübergehende Benachrichtigung einer Bedingungsvariable, die nur die in diesem Augenblick Wartenden weckt, mit einem Ereignis nachbauen will, landet außerdem leicht beim Zuverlässigkeitsproblem von PulseEvent. Für das Warten darauf, dass ein Zustand innerhalb eines Prozesses gilt, ist eine Bedingungsvariable die sichere Wahl.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.