前情回顧
前文我們介紹了六種內存順序,以及三種內存模型,本文通過代碼示例講解六種內存順序使用方法,并實現相應的內存模型。
-
全局一致性模型
-
同步模型(獲取和釋放)
-
松散模型
memory_order_seq_cst
- memory_order_seq_cst代表全局一致性順序,可以用于 store, load 和 read-modify-write 操作, 實現 sequencial consistent 的順序模型. 在這個模型下, 所有線程看到的所有操作都有一個一致的順序, 即使這些操作可能針對不同的變量, 運行在不同的線程.
std::atomic<bool> x, y;
std::atomic<int> z;
void write_x_then_y() {x.store(true, std::memory_order_relaxed); // 1y.store(true, std::memory_order_relaxed); // 2
}
void read_y_then_x() {while (!y.load(std::memory_order_relaxed)) { // 3std::cout << "y load false" << std::endl;}if (x.load(std::memory_order_relaxed)) { //4++z;}
}
void TestOrderRelaxed() {std::thread t1(write_x_then_y);std::thread t2(read_y_then_x);t1.join();t2.join();assert(z.load() != 0); // 5
}
上面的代碼load和store都采用的是memory_order_relaxed。線程t1按次序執行1和2,但是線程t2看到的可能是y為true,x為false。進而導致TestOrderRelaxed觸發斷言z為0.
如果換成memory_order_seq_cst則能保證所有線程看到的執行順序是一致的。
void write_x_then_y() {x.store(true, std::memory_order_seq_cst); // 1y.store(true, std::memory_order_seq_cst); // 2
}
void read_y_then_x() {while (!y.load(std::memory_order_seq_cst)) { // 3std::cout << "y load false" << std::endl;}if (x.load(std::memory_order_seq_cst)) { //4++z;}
}
void TestOrderSeqCst() {std::thread t1(write_x_then_y);std::thread t2(read_y_then_x);t1.join();t2.join();assert(z.load() != 0); // 5
}
上面的代碼x和y采用的是memory_order_seq_cst, 所以當線程t2執行到3處并退出循環時我們可以斷定y為true,因為是全局一致性順序,所以線程t1已經執行完2處將y設置為true,那么線程t1也一定執行完1處代碼并對t2可見,所以當t2執行至4處時x為true,那么會執行z++保證z不為零,從而不會觸發斷言。
實現 sequencial consistent 模型有一定的開銷. 現代 CPU 通常有多核, 每個核心還有自己的緩存. 為了做到全局順序一致, 每次寫入操作都必須同步給其他核心. 為了減少性能開銷, 如果不需要全局順序一致, 我們應該考慮使用更加寬松的順序模型.
memory_order_relaxed
memory_order_relaxed 可以用于 store, load 和 read-modify-write 操作, 實現 relaxed 的順序模型.
前文我們介紹過這種模型下, 只能保證操作的原子性和修改順序 (modification order) 一致性, 無法實現 synchronizes-with 的關系。
void TestOrderRelaxed() {std::atomic<bool> rx, ry;std::thread t1([&]() {rx.store(true, std::memory_order_relaxed); // 1ry.store(true, std::memory_order_relaxed); // 2});std::thread t2([&]() {while (!ry.load(std::memory_order_relaxed)); //3assert(rx.load(std::memory_order_relaxed)); //4});t1.join();t2.join();
}
Acquire-Release
oid TestReleaseAcquire() {std::atomic<bool> rx, ry;std::thread t1([&]() {rx.store(true, std::memory_order_relaxed); // 1ry.store(true, std::memory_order_release); // 2});std::thread t2([&]() {while (!ry.load(std::memory_order_acquire)); //3assert(rx.load(std::memory_order_relaxed)); //4});t1.join();t2.join();
}
Release sequences
我們再考慮一種情況,多個線程對同一個變量release操作,另一個線程對這個變量acquire,那么只有一個線程的release操作喝這個acquire線程構成同步關系。
void ReleasAcquireDanger2() {std::atomic<int> xd{0}, yd{ 0 };std::atomic<int> zd;std::thread t1([&]() {xd.store(1, std::memory_order_release); // (1)yd.store(1, std::memory_order_release); // (2)});std::thread t2([&]() {yd.store(2, std::memory_order_release); // (3)});std::thread t3([&]() {while (!yd.load(std::memory_order_acquire)); //(4)assert(xd.load(std::memory_order_acquire) == 1); // (5)});t1.join();t2.join();t3.join();
}
(2)和(4) ,(3)和(4)都可以構成同步關系
void ReleaseSequence() {std::vector<int> data;std::atomic<int> flag{ 0 };std::thread t1([&]() {data.push_back(42); //(1)flag.store(1, std::memory_order_release); //(2)});std::thread t2([&]() {int expected = 1;while (!flag.compare_exchange_strong(expected, 2, std::memory_order_relaxed)) // (3)expected = 1;});std::thread t3([&]() {while (flag.load(std::memory_order_acquire) < 2); // (4)assert(data.at(0) == 42); // (5)});t1.join();t2.join();t3.join();
}
memory_order_consume
void ConsumeDependency() {std::atomic<std::string*> ptr;int data;std::thread t1([&]() {std::string* p = new std::string("Hello World"); // (1)data = 42; // (2)ptr.store(p, std::memory_order_release); // (3)});std::thread t2([&]() {std::string* p2;while (!(p2 = ptr.load(std::memory_order_consume))); // (4)assert(*p2 == "Hello World"); // (5)assert(data == 42); // (6)});t1.join();t2.join();
}
單例模式改良
還記得我們之前用智能指針雙重檢測方式實現的單例模式嗎?我當時說過是存在線程安全問題的,看看下面這段單例模式
//利用智能指針解決釋放問題
class SingleAuto
{
private:SingleAuto(){}SingleAuto(const SingleAuto&) = delete;SingleAuto& operator=(const SingleAuto&) = delete;
public:~SingleAuto(){std::cout << "single auto delete success " << std::endl;}static std::shared_ptr<SingleAuto> GetInst(){// 1 處if (single != nullptr){return single;}// 2 處s_mutex.lock();// 3 處if (single != nullptr){s_mutex.unlock();return single;}// 4處single = std::shared_ptr<SingleAuto>(new SingleAuto);s_mutex.unlock();return single;}
private:static std::shared_ptr<SingleAuto> single;static std::mutex s_mutex;
};
為了解決這個問題,我們可以通過內存模型來解決
//利用智能指針解決釋放問題
class SingleMemoryModel
{
private:SingleMemoryModel(){}SingleMemoryModel(const SingleMemoryModel&) = delete;SingleMemoryModel& operator=(const SingleMemoryModel&) = delete;
public:~SingleMemoryModel(){std::cout << "single auto delete success " << std::endl;}static std::shared_ptr<SingleMemoryModel> GetInst(){// 1 處if (_b_init.load(std::memory_order_acquire)){return single;}// 2 處s_mutex.lock();// 3 處if (_b_init.load(std::memory_order_relaxed)) // 這里可以使用寬松的模型,因為上面的已經加鎖了,鎖的權力是最大的{s_mutex.unlock();return single;}// 4處single = std::shared_ptr<SingleMemoryModel>(new SingleMemoryModel);_b_init.store(true, std::memory_order_release);s_mutex.unlock();return single;}
private:static std::shared_ptr<SingleMemoryModel> single;static std::mutex s_mutex;static std::atomic<bool> _b_init ;
};
std::shared_ptr<SingleMemoryModel> SingleMemoryModel::single = nullptr;
std::mutex SingleMemoryModel::s_mutex;
std::atomic<bool> SingleMemoryModel::_b_init = false;