Mit der Acquire-Release-Semantik wird das C++-Speichermodell richtig spannend. Denn nun gilt es nicht mehr, die Synchronisation von Threads zu betrachten, sondern die Synchronisation auf der gleichen atomaren Variable in verschiedenen Threads.
Synchronisations- und Ordnungsbedingungen
Die Acquire-Release Semantik basiert auf einer entscheidenden Idee. Eine release-Operation synchronisiert sich mit einer acquire-Operation auf der gleichen atomaren Variable und erzeugt dazu noch eine Ordnungsbedingung. So können alle Lese- und Schreiboperationen nicht vor eine acquire-Operation, so können alle Lese- und Schreiboperationen nicht hinter eine Release-Operation verschoben werden. Was sind nun acquire- und release-Operationen? Das Lesen einer atomaren Variable, sei es mit load oder auch test_and_set ist eine acquire-Operation. Aber auch das Anfordern eines Lock ist eine acquire-Operation. Das Gegenteil gilt natürlich auch, das Freigeben eines Locks ist eine release-Operation. Entsprechend gilt diese für eine store oder auch clear Operation auf einer atomaren Variablen.
Den kleinen Abschnitt will ich gerne nochmal aus einem anderen Blickwinkel betrachten. Das Locken eines Mutex ist eine acquire-Operation, das Unlocken eines Mutex eine release-Operation. Bildlich gesprochen heißt dies, dass ein Variablenzugriff aus seinem kritischen Bereich nicht verschoben werden darf. Das gilt in beide Richtungen. Andererseits kann ein Variablenzugriff in einen kritischen Bereich verschoben werden, denn ein Variablenzugriff tauscht dadurch den unsicheren mit dem sicheren Bereich. Nach der bildlichen Beschreibung folgt mit leichter Verspätung das Bild dazu.
Habe ich es nicht versprochen? Die Acquire-Release-Semantik hilft uns, das Locken und Unlocken eines Mutex besser zu verstehen. Die gleiche Argumentation lässt sich natürlich auch auf das Starten eines Threads als acquire- und den join-Aufruf als release-Operation anwenden. Weiter geht es mit dem wait und notify_one-Aufruf einer Bedingungsvariable. wait ist in diesem konkreten Fall die acquire-, notify_one die release-Operation. Die Argumentation trifft auch zu, falls die Bedingungsvariable mehrere wartende Threads mit dem notify_all-Aufruf benachrichtigt. Hier ist wieder eine release-Operation im Einsatz.
Nach der Theorie lässt sich der Spinlock aus dem Artikel Das Atomare Flag effizienter schreiben, denn die Synchronisation findet auf der atomaren Variable flag statt.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
#include <atomic>
#include <thread>
class Spinlock{
std::atomic_flag flag;
public:
Spinlock(): flag(ATOMIC_FLAG_INIT) {}
void lock(){
while( flag.test_and_set(std::memory_order_acquire) );
}
void unlock(){
flag.clear(std::memory_order_release);
}
};
Spinlock spin;
void workOnResource(){
spin.lock();
// shared resource
spin.unlock();
}
int main(){
std::thread t(workOnResource);
std::thread t2(workOnResource);
t.join();
t2.join();
}
|
Der flag.clear- Aufruf in Zeile 14 ist eine release-, der flag.test_and_set in Zeile 10 ist eine acquire-Operation. Und - das ist nun ein alter Hut- die acquire- synchronisiert mit der release-Operation. Damit wird die teure Synchronisation zweier Threads mittels std::memory_ord_seq_cst durch die deutlich einfachere und daher auch performantere Acquire-Release-Semantik mittels std::memory_order_acquire und std::memory_order_release ersetzt. Das Verhalten ändert sich nicht.
Verwenden mehr als zwei Threads den Spinlock, ist die acquire-Semantik der lock-Methode nicht mehr ausreichend. In diesem Fall ist die lock-Operation ein acquire-release Operation. Daher muss das verwendete Speichermodell in Zeile 10 auf std::memory_order_acq_rel geändert werden.
Wie geht's weiter?
Im nächsten Artikel schaue ich mir genauer die Transitivität der Acquire-Release-Semantik an. Damit ist es möglich, Threads zu synchronisieren, die keine gemeinsame atomare Variable besitzen.
Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library". Hole dir dein E-Book. Unterstütze meinen Blog.
Weiterlesen...