Stimmt es, dass C ++ 0x ohne Semaphore kommt? Es gibt bereits einige Fragen zum Stapelüberlauf bezüglich der Verwendung von Semaphoren. Ich benutze sie (Posix-Semaphoren) die ganze Zeit, um einen Thread auf ein Ereignis in einem anderen Thread warten zu lassen:
void thread0(...)
{
doSomething0();
event1.wait();
...
}
void thread1(...)
{
doSomething1();
event1.post();
...
}
Wenn ich das mit einem Mutex machen würde:
void thread0(...)
{
doSomething0();
event1.lock(); event1.unlock();
...
}
void thread1(...)
{
event1.lock();
doSomethingth1();
event1.unlock();
...
}
Problem: Es ist hässlich und es kann nicht garantiert werden, dass Thread1 zuerst den Mutex sperrt (Da derselbe Thread einen Mutex sperren und entsperren sollte, können Sie Ereignis1 auch nicht sperren, bevor Thread0 und Thread1 gestartet wurden).
Also, da Boost auch keine Semaphoren hat, was ist der einfachste Weg, um das oben genannte zu erreichen?
Antworten:
Sie können einfach eine aus einem Mutex und einer Bedingungsvariablen erstellen:
quelle
while(!count_)
Schleife finden Sie unter en.wikipedia.org/wiki/Spurious_wakeup .Basierend auf der Antwort von Maxim Yegorushkin habe ich versucht, das Beispiel im C ++ 11-Stil zu erstellen .
quelle
cv.wait(lck, [this]() { return count > 0; });
Ich habe mich entschlossen, das robusteste / generischste C ++ 11-Semaphor zu schreiben, das ich im Stil des Standards so oft wie möglich schreiben konnte (beachten
using semaphore = ...
Sie, dass Sie normalerweise nur den Namen verwenden würden, dersemaphore
dem normalen Verwenden vonstring
not ähneltbasic_string
):quelle
wait_for
undwait_until
mit dem Prädikat geben einen booleschen Wert zurück (kein `std :: cv_status).std::size_t
ist ohne Vorzeichen, so dass das Dekrementieren unter Null UB ist und es immer sein wird>= 0
. IMHOcount
sollte ein seinint
.in Übereinstimmung mit Posix-Semaphoren würde ich hinzufügen
Und ich bevorzuge es sehr, einen Synchronisationsmechanismus auf einer bequemen Abstraktionsebene zu verwenden, anstatt immer das Zusammenfügen einer zusammengefügten Version mit grundlegenderen Operatoren zu kopieren.
quelle
Sie können auch cpp11-on-multicore ausprobieren - es verfügt über eine tragbare und optimale Semaphor-Implementierung.
Das Repository enthält auch andere Threading-Extras, die das C ++ 11-Threading ergänzen.
quelle
Sie können mit Mutex- und Bedingungsvariablen arbeiten. Sie erhalten exklusiven Zugriff mit dem Mutex. Prüfen Sie, ob Sie fortfahren möchten oder auf das andere Ende warten müssen. Wenn Sie warten müssen, warten Sie in einem Zustand. Wenn der andere Thread feststellt, dass Sie fortfahren können, signalisiert er den Zustand.
In der Boost :: Thread-Bibliothek gibt es ein kurzes Beispiel , das Sie höchstwahrscheinlich einfach kopieren können (die C ++ 0x- und Boost-Thread-Bibliotheken sind sehr ähnlich).
quelle
wait()
zu übersetzt „lock, Prüfanzahl wenn nicht Null Abnahme- und weiter, und wenn Null - Warte unter der Bedingung“ , währendpost
wäre „Schloss, Inkrementzähler, Signal, wenn es 0 "warKann auch ein nützlicher RAII-Semaphor-Wrapper in Threads sein:
Anwendungsbeispiel in der Multithread-App:
quelle
C ++ 20 wird endlich Semaphoren haben -
std::counting_semaphore<max_count>
.Diese haben (mindestens) die folgenden Methoden:
acquire()
(Blockierung)try_acquire()
(nicht blockierend, wird sofort zurückgegeben)try_acquire_for()
(nicht blockierend, dauert eine Dauer)try_acquire_until()
(Nicht blockierend, benötigt eine Zeit, um den Versuch zu beenden)release()
Dies ist noch nicht in cppreference aufgeführt, aber Sie können diese CppCon 2019-Präsentationsfolien lesen oder das Video ansehen . Es gibt auch den offiziellen Vorschlag P0514R4 , aber ich bin mir nicht sicher, ob dies die aktuellste Version ist.
quelle
Ich fand, dass shared_ptr und schwaches_ptr, ein langes mit einer Liste, die Arbeit erledigten, die ich brauchte. Mein Problem war, dass ich mehrere Clients hatte, die mit den internen Daten eines Hosts interagieren wollten. In der Regel aktualisiert der Host die Daten selbst. Wenn ein Client dies anfordert, muss der Host die Aktualisierung jedoch beenden, bis keine Clients mehr auf die Hostdaten zugreifen. Gleichzeitig kann ein Client einen exklusiven Zugriff anfordern, sodass weder andere Clients noch der Host diese Hostdaten ändern können.
Wie ich das gemacht habe, habe ich eine Struktur erstellt:
Jeder Kunde hätte ein Mitglied von folgenden:
Dann hätte der Host ein schwaches_Ptr-Mitglied für Exklusivität und eine Liste von schwachen_Ptrs für nicht exklusive Sperren:
Es gibt eine Funktion zum Aktivieren der Sperre und eine andere Funktion zum Überprüfen, ob der Host gesperrt ist:
Ich teste in LockUpdate, IsUpdateLocked und regelmäßig in der Update-Routine des Hosts auf Sperren. Das Testen auf eine Sperre ist so einfach wie das Überprüfen, ob der schwache_PTR abgelaufen ist, und das Entfernen von abgelaufenen Dateien aus der m_Locks-Liste (ich mache dies nur während des Host-Updates). Ich kann überprüfen, ob die Liste leer ist. Gleichzeitig wird die automatische Entsperrung automatisch aktiviert, wenn ein Client das shared_ptr zurücksetzt, an dem er festhält. Dies geschieht auch, wenn ein Client automatisch zerstört wird.
Der Gesamteffekt ist, dass Clients selten Exklusivität benötigen (normalerweise nur für Hinzufügungen und Löschungen reserviert). Meistens ist eine Anforderung an LockUpdate (false), dh nicht exklusiv, erfolgreich, solange (! M_exclusiveLock). Und ein LockUpdate (true), eine Anfrage nach Exklusivität, ist nur erfolgreich, wenn sowohl (! M_exclusiveLock) als auch (m_locks.empty ()).
Es könnte eine Warteschlange hinzugefügt werden, um zwischen exklusiven und nicht exklusiven Sperren abzumildern. Bisher gab es jedoch keine Kollisionen. Daher möchte ich warten, bis die Lösung hinzugefügt wird (meistens, damit ich eine reale Testbedingung habe).
Bisher funktioniert dies gut für meine Bedürfnisse; Ich kann mir vorstellen, dass dies erweitert werden muss, und einige Probleme, die bei einer erweiterten Verwendung auftreten können, waren jedoch schnell zu implementieren und erforderten nur sehr wenig benutzerdefinierten Code.
quelle
Falls sich jemand für die Atomversion interessiert, finden Sie hier die Implementierung. Die Leistung wird besser erwartet als die Version mit Mutex- und Bedingungsvariablen.
quelle
wait
Code muss mehrmals wiederholt werden. Wenn es endgültig entsperrt wird, wird die Mutter aller falsch vorhergesagten Zweige benötigt, da die Schleifenvorhersage der CPU mit Sicherheit vorhersagt, dass es wieder eine Schleife geben wird. Ich könnte noch viele weitere Probleme mit diesem Code auflisten.wait
Schleife verbraucht beim Drehen CPU-Mikroausführungsressourcen. Angenommen, es befindet sich im selben physischen Kern wie der Thread, der es sollnotify
- es wird diesen Thread fürchterlich verlangsamen.wait
Schleife für dasselbe Semaphor. Beide schreiben mit voller Geschwindigkeit in dieselbe Cache-Zeile, wodurch andere Kerne durch Sättigung der Inter-Core-Busse zum Crawlen gebracht werden können.