効率的で正しいマルチスレッドアプリケーションを作成するには、実行スレッド間に存在するメモリ同期のメカニズム、ミューテックス、結合スレッドなどのマルチスレッドプログラミングの要素によって提供される保証を知ることが非常に重要です。これは、さまざまなプロセッサアーキテクチャに最適なマルチスレッドコードを提供するために複雑になるように設計されたC ++メモリモデルに特に当てはまります。ちなみに、LLVM上に構築されているRustプログラミング言語は、C ++と同じメモリモデルを使用しています。したがって、この記事の内容は、両方の言語のプログラマーに役立ちます。ただし、すべての例はC ++になります。についてお話しますがstd::atomic
、std::memory_order
その上に3匹の象が原子です。
C++11 C++, . . , . . , , . - ( ). , , : , . . , , . - . x86-64 ARM , .
C++ , ++11 , , .
: C++ — "" , . C++ , undefined behavior (UB), , .
, C++, , . , .
, . (std::atomic
), .. "" . , (std::mutex
) , , . , .
, C++ , . ?
… .
.
.
— , , . . std::atomic, : load
, store
, fetch_add
, compare_exchange_*
. — read-modify-write , .
read-modify-write , . 0, link:
static int v1 = 0;
static std::atomic<int> v2{ 0 };
int add_v1() {
return ++v1;
/* Generated x86-64 assembly:
mov eax, DWORD PTR v1[rip]
add eax, 1
mov DWORD PTR v1[rip], eax
*/
}
int add_v2() {
return v2.fetch_add(1);
/* Generated x86-64 assembly:
mov eax, 1
lock xadd DWORD PTR _ZL2v2[rip], eax
*/
}
v1
int : read-modify-write. , v1
. v2
lock , , , v2
, , .
. , , . . . , , . .
. , , . , , , , . UB.
, :
, ,
, . C++ . : relaxed
, release/acquire
sequential consistency
. .
,
— relaxed
. , . :
""
thread2
"" ,thread1
thread1
thread2
relaxed
. 1, link:
std::atomic<size_t> counter{ 0 };
// process can be called from different threads
void process(Request req) {
counter.fetch_add(1, std::memory_order_relaxed);
// ...
}
void print_metrics() {
std::cout << "Number of requests = " << counter.load(std::memory_order_relaxed) << "\n";
// ...
}
. 2, link:
std::atomic<bool> stopped{ false };
void thread1() {
while (!stopped.load(std::memory_order_relaxed)) {
// ...
}
}
void stop_thread1() {
stopped.store(true, std::memory_order_relaxed);
}
thread1
, stop_thread1
. , thread1
() stopped
true
.
relaxed
. 3, link:
std::string data;
std::atomic<bool> ready{ false };
void thread1() {
data = "very important bytes";
ready.store(true, std::memory_order_relaxed);
}
void thread2() {
while (!ready.load(std::memory_order_relaxed));
std::cout << "data is ready: " << data << "\n"; // potentially memory corruption is here
}
, thread2
data
, ready
, .. relaxed
.
" " (sequential consistency, seq_cst
) . :
thread1
thread2
.
( )
thread1
,store
,load
thread2
seq_cst
, , .
C++ , .. . seq_cst
, . , x86-64 seq_cst
, ARM .
. 4, [1], link:
std::atomic<bool> x, y;
std::atomic<int> z;
void thread_write_x() {
x.store(true, std::memory_order_seq_cst);
}
void thread_write_y() {
y.store(true, std::memory_order_seq_cst);
}
void thread_read_x_then_y() {
while (!x.load(std::memory_order_seq_cst));
if (y.load(std::memory_order_seq_cst)) {
++z;
}
}
void thread_read_y_then_x() {
while (!y.load(std::memory_order_seq_cst));
if (x.load(std::memory_order_seq_cst)) {
++z;
}
}
, , z
1
2
, thread_read_x_then_y
thread_read_y_then_x
"" x
y
. : x = true
, y = true
, y = true
, x = true
.
seq_cst
relaxed
acquire/release
, . seq_cst
, : seq_cst
. 1 2 , relaxed
seq_cst
, 3 .
. Acquire/Release
acquire/release
. : memory_order_acquire
memory_order_release
. :
release
,acquire
thread1
,release
,acquire
thread2
release
thread1
,acquire
thread2
, , . , 4 store
memory_order_release
, load
memory_order_acquire
, z
0, 1 2. , , store
x
y
, thread_read_x_then_y
thread_read_y_then_x
. , load
store
3. , .. ( seq_cst
), .
release
, , . acquire
, "" , . release
acquire
, UB .
, , lock
. spinlock
. , , . 5, link:
class mutex {
public:
void lock() {
bool expected = false;
while(!_locked.compare_exchange_weak(expected, true, std::memory_order_acquire)) {
expected = false;
}
}
void unlock() {
_locked.store(false, std::memory_order_release);
}
private:
std::atomic<bool> _locked;
};
lock()
false true acquire
. compare_exchage_weak
strong
, cppreference. unlock()
false release
. , , . , unlock()
, lock()
. . , .
, Double Checked Locking Anti-Pattern [2]. 6, link:
struct Singleton {
// ...
};
static Singleton* singleton = nullptr;
static std::mutex mtx;
static bool initialized = false;
void lazy_init() {
if (initialized) // early return to avoid touching mutex every call
return;
std::unique_lock l(mtx); // `mutex` locks here (acquire memory)
if (!initialized) {
singleton = new Singleton();
initialized = true;
}
// `mutex` unlocks here (release memory)
}
: Singleton
. , . .. , singleton
read-only , if (initialized) return
. , x86-64. C++. :
void thread1() {
lazy_init();
singleton->do_job();
}
void thread2() {
lazy_init();
singleton->do_job();
}
:
1. thread1
-> :
lock (
acquire
)singleton = ..
initialized = true
unlock (
release
)
2. thread2
:
if(initalized)
true
(,initialized
)singleton->do_job()
segmentation fault
(singleton
thread1
)
, , .
acquire/release
acquire/release
, . .
| |
| |
| lock , unlock. |
|
|
. [1].
? : , std::promise::set_value
std::future::wait
, , , , , set_value
. , -, . , , , , , .
C++ , . , . , , C++. volatile bool
, , , read-modify-write , . , . , !
[1] Anthony Williams. C++ Concurrency in Action. https://www.amazon.com/C-Concurrency-Action-Practical-Multithreading/dp/1933988770
[2] Tony vanEerd。C ++メモリモデルとロックフリープログラミング。https://www.youtube.com/watch?v=14ntPfyNaKE