
Untuk menulis aplikasi multithread yang efisien dan benar, sangat penting untuk mengetahui mekanisme apa yang ada untuk sinkronisasi memori antar thread eksekusi, jaminan apa yang diberikan oleh elemen pemrograman multithread, seperti mutex, join thread, dan lain-lain. Hal ini terutama berlaku untuk model memori C ++, yang dirancang agar kompleks guna memberikan kode multi-thread yang optimal untuk berbagai arsitektur prosesor. Ngomong-ngomong, bahasa pemrograman Rust, yang dibangun di LLVM, menggunakan model memori yang sama seperti di C ++. Oleh karena itu, materi dalam artikel ini akan bermanfaat bagi programmer dalam kedua bahasa tersebut. Tetapi semua contoh akan menggunakan C ++. Saya akan berbicara tentang std::atomic, std::memory_orderdan yang tiga gajah adalah atomnya.
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"" ,thread1thread1thread2
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) . :
thread1thread2.
( )
thread1,store,loadthread2
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,acquirethread1,release,acquirethread2releasethread1,acquirethread2
, , . , 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 = trueunlock (
release)
2. thread2:
if(initalized)true(,initialized)singleton->do_job()segmentation fault(singletonthread1)
, , .
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 van Eerd. C ++ Model Memori & Pemrograman Bebas Kunci. https://www.youtube.com/watch?v=14ntPfyNaKE