In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-29 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)05/31 Report--
This article introduces the knowledge of "what is Memory Order in C++ atomic atomic programming". In the operation of practical cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
Overview
However, synchronization based on kernel objects can lead to expensive context switching (switching from user mode to kernel mode, which takes more than 1000 cpu cycles). You need to use another method-- atomic instructions.
Access control to resources cannot be achieved by atomic technology alone, and even simple counting operations may result in crash of seemingly correct code.
The key here is that the rearrangement of instructions implemented by the compiler and cpu results in a change in the order of reading and writing. As long as there are no dependencies, the instructions that follow in the code may go ahead, as both the compiler and CPU do.
Note 1: single-threaded code does not need to care about disorder. Because disorder should at least guarantee this principle: the execution behavior of a single-threaded program cannot be changed.
Note 2: kernel object multithreading programming is designed to prevent disorder in their call points (memory barrier is implicitly included), and there is no need to consider disorder.
Note 3: when using thread synchronization in user mode, the effect of disorder will no doubt be revealed.
Programmers can control the behavior of rearranging instructions implemented by compilers and cpu at the programming language level by using six kinds of memory order provided by cymbals 11 atomic
In multithreaded programming, four synchronization models can be combined by reading and writing atomic variables through these flag bits:
Relaxed ordering
Release-Acquire ordering
Release-Consume ordering
Sequentially-consistent ordering
By default, std::atomic uses Sequentially-consistent ordering (the strictest synchronization model). However, in some scenarios, the rational use of the other three ordering allows the compiler to optimize the generated code, thereby improving performance.
Relaxed ordering
Under this model, both load () and store () of std::atomic take the memory_order_relaxed parameter. Relaxed ordering only guarantees that load () and store () are atomic operations, and apart from that, it does not provide any cross-thread synchronization.
Let's look at a simple example:
Std::atomic x = 0; / / global variablestd::atomic y = 0; / / global variable Thread-1: Thread-2:r1 = y.load (memory_order_relaxed); / / AR2 = x.load (memory_order_relaxed); / / Cx.store (R1, memory_order_relaxed); / / B y.store (42, memory_order_relaxed); / / D
After executing the above program, R1 = R2 = 42 may appear. This is not difficult to understand because the compiler allows you to adjust the execution order of C and D.
If the execution order of the program is D-> A-> B-> C, then R1 = = R2 = = 42 will appear.
If an operation requires only atomic operations and no other guarantees of synchronization, you can use Relaxed ordering. Program counter is a typical application scenario.
# include # include std::atomic cnt = {0}; void f () {for (int n = 0; n < 1000; + + n) {cnt.fetch_add (1, std::memory_order_relaxed);}} int main () {std::vector v; for (int n = 0; n < 10; + + n) {v.emplace_back (f) } for (auto& t: v) {t.join ();} assert (cnt = = 10000); / / never failed return 0;} Release-Acquire ordering
In this model, store () uses memory_order_release, while load () uses memory_order_acquire. This model has two effects. The first is that it can limit the rearrangement of CPU instructions:
(1) all read and write operations before store () are not allowed to be moved after this store (). / / write-release semantics
(2) all read and write operations after load () are not allowed to be moved in front of the load (). / / read-acquire semantics
The model ensures that if the value of store () of Thread-1 is successfully reached by load () of Thread-2, then all writes to memory by Thread-1 before store () are visible to Thread-2.
The following example illustrates the principle of this model:
# include # include std::atomic ready {false}; int data = 0: void producer () {data = 100; / / A ready.store (true, std::memory_order_release); / / B} void consumer () {while (! void (std::memory_order_acquire)) / / C; assert (data = = 100) / / never failed / / D} int main () {std::thread T1 (producer); std::thread T2 (consumer); t1.join (); t2.join (); return 0;}
Let's analyze this process:
First of all, An is not allowed to be moved behind B.
Similarly, D is not allowed to be moved in front of C.
When C exits from the while loop, C reads the value of B store (), and Thread-2 guarantees that Thread-1 can see all writes before B (that is, A).
Using Release-Acquire ordering to implement double-checked lock mode (DLCP)
The following individual items are illustrated as an example:
Class Singleton {public: static Singleton* get_instance () {Singleton* tmp = instance_.load (std::memory_order_acquire); if (tmp = = nullptr) {std::unique_lock lk (mutex_); tmp = instance_; if (tmp = = nullptr) {tmp = new Singleton () Instance_.store (std::memory_order_release);}} return tmp;} private: Singleton () = default; static std::atomic instance_; static std::mutex mutex_;}; spin lock (Spinlock) using Release-Acquire ordering
Acquiring and releasing semantics is the basis for implementing locks (Spinlock, Mutex, RWLock,...). All areas contained by [Read Acquire,Write Release] form a critical area, and memory operations in the critical area will not be performed out of order outside the critical area.
Read-acquire (to determine whether to lock or not, otherwise loop to wait)
All memory operation stay between the line (critical area)
Write-release (release lock)
The implementation code is as follows:
# include class simple_spin_lock {public: simple_spin_lock () = default; void lock () {while (flag.test_and_set (std::memory_order_acquire)) continue;} void unlock () {flag.clear (std::memory_order_release);} private: simple_spin_lock (const simple_spin_lock&) = delete Simple_spin_lock& operator = (const simple_spin_lock&) = delete; std::atomic_flag flag = ATOMIC_FLAG_INIT;}
① 's operation on std::atomic_flag is atomic, which ensures that only one thread can lock successfully at the same time, and all the other threads are in the while loop.
② uses the acquire memory barrier, so lock has acquisition semantics
③ uses the release memory barrier, so unlock has release semantics
Release-Consume ordering
In this model, store () uses memory_order_release, while load () uses memory_order_consume. This model has two effects. The first is that it can limit the rearrangement of CPU instructions:
(1) all read and write operations before store () are not allowed to be moved after this store ().
(2) all read and write operations that depend on this atomic variable after load () are not allowed to be moved in front of the load ().
Note: read and write operations that do not depend on this atomic variable may rearrange CPU instructions.
The following example illustrates the principle of this model:
# include # include std::atomic ptr;int data;// thread1void producer () {std::string* p = new std::string ("Hello"); / / A data = 42; / / B ptr.store (p, std::memory_order_release); / / C} / / thread2void consumer () {std::string* p2; while (! (p2 = ptr.load (std::memory_order_consume)) / / D Assert (* p2 = = "Hello"); / / E always true: * p2 carries dependency from ptr assert (data = = 42); / / F may be false: data does not carry dependency from ptr} int main () {std::thread T1 (producer); std::thread T2 (consumer); t1.join (); t2.join (); return 0;} Sequentially-consistent ordering
All atomic operations that take memory_order_seq_cst as a parameter (not limited to the same atomic variable) have a global total order for all threads
And other operations (including non-atomic variable operations) between two adjacent memory_order_seq_cst atomic operations cannot be reorder beyond these two adjacent operations.
Memory Orderenum class EMemoryOrder {/ / Provides no guarantees that the operation will be ordered relative to any other operation under UE4. Relaxed, / / Establishes a single total order of all other atomic operations marked with this. The SequentiallyConsistent / / Load and Store functions default to this type}; that's all for "what is Memory Order in C++ atomic atomic programming". Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 209
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.