之前一段時間偶然在 B 站上刷到了南京大學(xué)蔣炎巖(jyy)老師在直播操作系統(tǒng)網(wǎng)課。點(diǎn)進(jìn)直播間看了一下發(fā)現(xiàn)這個老師實(shí)力非凡,上課從不照本宣科,而且旁征博引又不吝于親自動手演示,于是點(diǎn)了關(guān)注。后來開始看其網(wǎng)課錄播,其中一節(jié)的標(biāo)題吸引了我,多處理器編程:從入門到放棄 (線程庫;現(xiàn)代處理器和寬松內(nèi)存模型)?!岸嗵幚砥骶幊獭边@個詞讓我聯(lián)想到去年看的《The Art of Multiprocessor Programming》,于是仔細(xì)看了一下這節(jié)網(wǎng)課。里面介紹到了一個試?yán)?write_x_read_y,它是用 C 語言和內(nèi)聯(lián)匯編寫的,它用來說明運(yùn)行期指令重排。這個試?yán)軌虺晒τ^測到運(yùn)行期指令重排現(xiàn)象。這讓我不得不佩服 jyy 的實(shí)踐精神。之前看了一些介紹 C++ 內(nèi)存模型的文章,沒有一個能用可復(fù)現(xiàn)的完整代碼說明問題的,全部都是說這段代碼可能出現(xiàn) xx 結(jié)果,沒有實(shí)際的執(zhí)行結(jié)果。在 C++ 內(nèi)存模型中,這個測試用例除了能夠說明運(yùn)行期指令重排,也能用于說明 happens-before consistency 和 sequential consistency 的差別。于是嘗試用 C++ Atomic 來實(shí)現(xiàn)這段代碼,看看能不能觀測到預(yù)期結(jié)果。
成都創(chuàng)新互聯(lián)從2013年成立,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元鐵力做網(wǎng)站,已為上家服務(wù),為鐵力各地企業(yè)和個人服務(wù),聯(lián)系電話:13518219792
首先線程庫 pthread 替換為 std::thread,內(nèi)聯(lián)匯編替換為 std::atomic,且 load 和 store 操作全部使用最弱的 std::memory_order_relaxed
內(nèi)存序。完整的代碼如下:
// write_x_read_y.cpp
#include <atomic>
#include <thread>
#include <stdio.h>
static std::atomic_int flag{0};
inline void wait_flag(int id)
{
while (!(flag & (0x1 << id))) {}
}
inline void clear_flag(int id)
{
flag.fetch_and(~(0x1 << id));
}
std::atomic_int x{0}, y{0};
void write_x_read_y()
{
while (true) {
wait_flag(0);
x.store(1, std::memory_order_relaxed); // t1.1
int v = y.load(std::memory_order_relaxed); // t1.2
printf("%d ", v);
clear_flag(0);
}
}
void write_y_read_x()
{
while (true) {
wait_flag(1);
y.store(1, std::memory_order_relaxed); // t2.1
int v = x.load(std::memory_order_relaxed); // t2.2
printf("%d ", v);
clear_flag(1);
}
}
int main()
{
std::thread t1(write_x_read_y), t2(write_y_read_x);
while (true) {
x = 0, y = 0;
flag = 0b11;
while (flag) {}
printf("\n");
fflush(stdout);
}
t1.join();
t2.join();
}
注意這段代碼要開啟代碼優(yōu)化才能觀測到運(yùn)行期指令重排,這里選擇 O2
g++ -o write_x_read_y.out -O2 -pthread -std=c++11 -Wall -Wextra write_x_read_y.cpp
然后使用 jyy 視頻里使用的 Unix 命令進(jìn)行測試并整理結(jié)果
./write_x_read_y.out | head -n | sort | uniq -c
以下結(jié)果是在虛擬機(jī)環(huán)境中執(zhí)行得到的。宿主機(jī) CPU 型號為 AMD Ryzen 7 5800X,OS 為 Windows 10 x64,虛擬機(jī)是 Rocky Linux 8.6。
0 0
0 1
1109 1 0
2 1 1
成功觀測到“0 0”。假設(shè)程序按照簡單交叉執(zhí)行,執(zhí)行結(jié)果只可能是“0 1”、“1 0”、“1 1”這三種,不可能出現(xiàn)“0 0”。也就是說發(fā)生了運(yùn)行期指令重排。
接下來,將 std::memory_order_relaxed
替換為 std::memory_order_release
和 std::memory_order_acquire
,再測一遍
x.store(1, std::memory_order_release); // t1.1
int v = y.load(std::memory_order_acquire); // t1.2
printf("%d ", v);
y.store(1, std::memory_order_release); // t2.1
int v = x.load(std::memory_order_acquire); // t2.2
printf("%d ", v);
測試結(jié)果為:
0 0
0 1
1 0
2 1 1
又出現(xiàn)了“0 0”,也就說明這個試?yán)裏o法區(qū)分 relaxed memory model 和 happens-before consistency。這也與理論相符,雖然 t1.1 happens-before t2.2、t2.1 happens-before t1.2,但是卻無法借此推導(dǎo)出約束關(guān)系來限制執(zhí)行結(jié)果。“0 0”依然有可能出現(xiàn)。
接下來替換為 std::memory_order_seq_cst
x.store(1, std::memory_order_seq_cst); // t1.1
int v = y.load(std::memory_order_seq_cst); // t1.2
printf("%d ", v);
y.store(1, std::memory_order_seq_cst); // t2.1
int v = x.load(std::memory_order_seq_cst); // t2.2
printf("%d ", v);
測試結(jié)果為:
0 1
151 1 0
1 1
這次“0 0”并沒有出現(xiàn),運(yùn)行期指令重排沒有被觀測到。這與理論相符,使用 std::memory_order_seq_cst
的所有原子操作可以視為簡單交叉執(zhí)行,也就是 sequential consistency?!? 0”不可能出現(xiàn)。
write_x_read_y 這個試?yán)芎玫卣f明了 C++ 內(nèi)存模型中的 happens-before consistency 和 sequential consistency 的區(qū)別。它的代碼片段常見于各種相關(guān)文章中,卻沒有完整的代碼和實(shí)際的測試結(jié)果。這下也算補(bǔ)全了 C++ 內(nèi)存模型知識的一塊拼圖。
當(dāng)前標(biāo)題:C++ 內(nèi)存模型 write_x_read_y 試?yán)龢?gòu)造
轉(zhuǎn)載源于:http://chinadenli.net/article22/dsoigjc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站策劃、手機(jī)網(wǎng)站建設(shè)、自適應(yīng)網(wǎng)站、定制開發(fā)、關(guān)鍵詞優(yōu)化、標(biāo)簽優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)