C和C++中的volatile、記憶體屏障和CPU快取一致性協議MESI
目錄
1) 結果1(非優化編譯:g++ -g -o x x.cpp -O0) 5
2) 結果2(優化編譯:g++ -g -o x x.cpp -O2) 6
1) C++標準委員會(The C++ Standards Committee) 10
5) Intel記憶體模型(Intel Memory Model) 10
7) Sequential Consistency &TSO 10
10) 編譯器屏障(Compiler Barriers) 10
11) C ++ 11中memory_order_consume的作用 10
本文內容主要針對Linux,而且主要是x86環境。先看一常見用法:
class Thread { public: X() : _stop(false) { }
void stop() { _stop = true; }
void run() { while (!_stop) { work(); } }
private: volatile bool _stop; }; |
然後看看標準C++基金會(https://isocpp.org)怎麼說的(官方連結):
- 與平臺無關的多執行緒程式,volatile幾乎無用(Java和C#中的volatile除外);
- volatile不保證原子性(一般需使用CPU提供的LOCK指令);
- volatile不保證執行順序;
- volatile不提供記憶體屏障(Memory Barrier)和記憶體柵欄(Memory Fence);
- 多核環境中記憶體的可見性和CPU執行順序不能通過volatile來保障,而是依賴於CPU記憶體屏障。
注:volatile誕生於單CPU核心時代,為保持相容,一直只是針對編譯器的,對CPU無影響。
volatile在C/C++中的作用:
- 告訴編譯器不要將定義的變數優化掉;
- 告訴編譯器總是從快取取被修飾的變數的值,而不是暫存器取值。
就前言中的程式碼,可移植的實現方式為:
#include <atomic>
class Thread { public: X() : _stop(false) { }
void stop() { _stop = true; }
void run() { while (!_stop) { work(); } }
private: std::atomic<bool> _stop; }; |
不過這要求至少C++11,否則可使用相容C++98的實現CAtomic<bool>替代:
https://github.com/eyjian/libmooon/blob/master/include/mooon/sys/atomic.h,實際上Linux核心原始碼都帶有這些基礎設施。
- 訊號處理程式;
- 與硬體打交道(嵌入式開發用得多);
- 和setjmp、longjmp配合(請參見:http://www.cplusplus.com/reference/csetjmp/setjmp/),原因同訊號處理。
記憶體屏障,也叫記憶體柵欄(Memory Fence)。分編譯器屏障(Compiler Barrier,也叫優化屏障)和CPU記憶體屏障,其中編譯器屏障只對編譯器有效,它們的定義如下表所示(限x86,其它架構並不相同):
#define barrier() \ __asm__ __volatile__("":::"memory") |
編譯器屏障,如果是GCC,則可用__sync_synchronize()替代 |
#define mb() \ __asm__ __volatile__("mfence":::"memory") |
CPU記憶體屏障,還分讀寫記憶體屏障 |
#define rmb() \ __asm__ __volatile__("lfence":::"memory") |
CPU讀(Load)記憶體屏障 |
#define wmb() \ __asm__ __volatile__("sfence":::"memory") |
CPU寫(Store)記憶體屏障 |
x86架構的CPU記憶體屏障程式碼可在核心原始碼的arch/x86/include/asm/barrier.h中找到。對於原子操作,需要使用CPU提供的“lock”指令,對於CPU亂序需使用CPU記憶體屏障。
程式碼順序 |
編譯器順序 |
CPU順序 |
a=1; b=x; c=z; d=2; |
a=1; d=2; b=x; c=z; |
b=x; c=z; a=1; d=2; |
推薦資料:
https://mariadb.org/wp-content/uploads/2017/11/2017-11-Memory-barriers.pdf
記憶體屏障進一步還分讀記憶體屏障和寫記憶體屏障等,對非核心開發者來說,記憶體屏障的主要應用場景為無鎖程式設計(Lock-free),因為像pthread_mutx_t等實際已經包含了“Lock”和“Memory Barrier”,所以無需再操心。
在C/C++中,goto關鍵詞只能函式內的區域性跳轉,函式間的跳轉需要使用setjmp和longjmp,這也是有些協程庫基於setjmp和longjmp實現的原因。
- setjmp
儲存上下文,包括訊號掩碼,類似於setcontext。該函式的返回值比較特別,第一次返回0,第二次返回的longjmp第二個引數值(如果longjmp第二個引數值為0,則返回值為1,這樣方便區分於第一次返回)。
- longjmp
該函式從不返回,而是跳回到setjmp儲存點,類似於swapcontext。如果沒有先呼叫setjmp,則longjmp的行為是未定義的。C++程式碼可能還會執行棧展開(Unwinding),如果呼叫了任何非平凡解構函式(non-trivial destructors,需顯示處理的解構函式,如記憶體釋放),也會導致未定義的行為。
/* x.cpp */ /* setjmp example: error handling */ #include <stdio.h> /* printf, scanf */ #include <stdlib.h> /* exit */ #include <setjmp.h> /* jmp_buf, setjmp, longjmp */
struct X { X() { fprintf(stderr, "X::ctor\n"); } ~X() { fprintf(stderr, "X::dtor\n"); } };
int main() { jmp_buf env; int val = -1; int m = -1; volatile int n = -1; X x;
val = setjmp(env); fprintf(stderr, "setjmp return: %d\n", val); if (val) { fprintf(stderr, "m: %d\n", m); fprintf(stderr, "n: %d\n", n); exit(val); } else { m = 2018; n = 2018;
/* code here */ longjmp(env, 19); /* signaling an error */ return 0; } } |
上例程式碼執行有兩種輸出結果:
X::ctor setjmp return: 0 setjmp return: 19 m: 2018 n: 2018 |
非優先編譯時,總是從記憶體取值。
X::ctor setjmp return: 0 setjmp return: 19 m: -1 n: 2018 |
因m未加volatile修飾,直接讀取暫存器值,因此結果是-1。從這裡也可以看出,即使是單執行緒程式,volatile也是必要的,也說明volatile並不是完全沒用,只是它不能幫助解決多執行緒的原子性、記憶體屏障和CPU亂序執行。
另外可發現,上列程式碼的類X的析構未執行,但若將exit改成return,則會執行類X的析構,遇到“}”和“return”時,編譯器會安插解構函式呼叫。
注:LOAD為讀操作,STORE為寫操作。
|
Loads reordered after loads |
Loads reordered after stores |
Stores reordered after stores |
Stores reordered after loads |
Atomic reordered with loads |
Atomic reordered with stores |
Dependent loads reordered |
Incoherent instruction cache pipeline |
Alpha |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
Y |
ARMv7 |
Y |
Y |
Y |
Y |
Y |
Y |
|
Y |
PA-RISC |
Y |
Y |
Y |
Y |
|
|
|
|
POWER |
Y |
Y |
Y |
Y |
Y |
Y |
|
Y |
SPARC RMO |
Y |
Y |
Y |
Y |
Y |
Y |
|
Y |
SPARC PSO |
|
|
Y |
Y |
|
Y |
|
Y |
SPARC TSO |
|
|
Y |
|
|
|
|
Y |
x86 |
|
|
Y |
|
|
|
|
Y |
x86 oostore |
Y |
Y |
Y |
Y |
|
|
|
Y |
AMD64 |
|
|
|
Y |
|
|
|
|
IA-64 |
Y |
Y |
Y |
Y |
Y |
Y |
|
Y |
z/Architecture |
|
|
|
Y |
|
|
|
|
四種SMP架構的CPU記憶體一致性模型:
- 順序一致性模型(SC,Sequential Consistency,所有讀取和所有寫入都是有序的);
- 寬鬆一致性模型(RC,Relaxed Consistency,允許某些可以重排序),ARM和POWER屬於這類;
- 弱一致性模型(WC,Weak Consistency,讀取和寫入任意重新排序,僅受顯式記憶體屏障限制);
- 完全儲存排序(TSO,Total Store Ordering),SPARC和x86屬於這種型別,只有“store load”一種情況會發生重排序,其它情況和SC模型一樣。
x86-TSO是Intel推出的一種CPU記憶體一致性模型,特點是隻有“Store Load”一種情況會重排序,也就是“Load”可以排(亂序)在“Store”的前面,因此不需要“Load Load”、“Store Store”和“Load Store”這三種屏障。
- “Store Load”屏障的作用是:確保“前者刷入記憶體”的資料對“後者載入資料”是可見的;
- “Load Load”屏障的作用是:確保“前者裝載資料”先於“後者裝載指令”;
- “Store Store”屏障的作用是:確保“前者資料”先於“後者資料”刷入記憶體,且“前者刷入記憶體的資料”對“後者是可見的”;
- “Load Store”屏障的作用是:確保“前者裝載資料”先於“後者重新整理資料到記憶體”。
enum memory_order { memory_order_relaxed, // 寬鬆一致性模型,不對執行順序做任何保證 memory_order_consume, // (讀操作)本執行緒所有後續有關本操作的必須在本操作完成後執行 memory_order_acquire, // (讀操作)本執行緒所有後續的讀操作必須在本條操作完成才能執行 memory_order_release, // (寫操作)本執行緒所有之前的寫操作完成後才執行本操作 memory_order_acq_rel, // (讀-修改-寫)同時包含Acquire和Release memory_order_seq_cst // (讀-修改-寫,預設型別)順序一致性模型,全部順序執行 }; |
// 預設記憶體順型別為“memory_order_seq_cst” std::atomic<bool> std::atomic<int32_t> std::atomic<int64_t> 。。。。。。 |
附1:CPU、快取和主存
附2:SMP對稱多處理器結構
多個CPU對稱工作沒有區別,無主次或從屬關係,平等地訪問記憶體、外設和一個作業系統,共享全部資源,如匯流排、記憶體和I/O系統等,因此也被稱為一致儲存器訪問結構(UMA : Uniform Memory Access)。
其它的架構有:
- NUMA(Non-Uniform Memory Access,非統一記憶體訪問),基本特徵是將CPU分成多個模型,每個模型多個CPU組成,具有獨立的本地記憶體和I/O槽口等;
- MPP(Massive Parallel Processing,海量並行處理結構),基本特徵是由多個SMP伺服器(每個SMP伺服器稱節點)通過節點網際網路絡連線而成,每個節點只訪問自己的本地資源(記憶體、儲存等),是一種完全無共享(Share Nothing)結構。
附3:線上C++編譯器
- https://www.tutorialspoint.com/compile_cpp_online.php
- https://www.jdoodlecom/online-compiler-c++
- http://coliru.stacked-crooked.com/
- https://www.onlinegdb.com/online_c++_compiler
- https://ideone.com/
- http://cpp.sh/
- https://www.cppbuzz.com/compiler/online-c++-compiler
- https://www.codechef.com/ide/
- https://repl.it/repls/ZestyNaturalMatch
- https://www.codiva.io
- http://codepad.org/
- https://cppinsights.io/
- https://tio.run/#cpp-gcc
- http://www.compileonline.com/
聚合了各種語言線上編譯。
還支援其它眾多語言線上編譯。
附4:資源連結
http://www.open-std.org/jtc1/sc22/wg21/
https://www.kernel.org/doc/html/latest/process/volatile-considered-harmful.html
https://en.wikipedia.org/wiki/Intel_Memory_Model
https://www.cl.cam.ac.uk/~pes20/weakmemory/x86tso-paper.tphols.pdf(A Better x86 Memory Model: x86-TSO)
http://homepages.inf.ed.ac.uk/vnagaraj/papers/hpca14.pdf(TSO-CC: Consistency directed cache coherence for TSO)
https://www.cis.upenn.edu/~devietti/classes/cis601-spring2016/sc_tso.pdf
https://en.wikipedia.org/wiki/Write_buffer
https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html(IA-32:Intel Architecture 32-bit,即32位x86)
https://preshing.com/20140709/the-purpose-of-memory_order_consume-in-cpp11/
https://en.wikipedia.org/wiki/MESI_protocol
https://en.wikipedia.org/wiki/MESIF_protocol
M 修改(Modified) |
該Cache line(快取行)有效,資料被修改(dirty)了,和主存中的資料不一致,資料只存在於本Cache中 |
E 獨佔互斥(Exclusive) |
該Cache line只被快取在該CPU的快取中,它是未被修改過的(clean),與主存中資料一致 |
S 共享(Shared) |
該Cache line有效,資料和記憶體中的資料一致,資料存在於很多Cache中 |
I 無效(Invalid) |
該Cache line無效,可能有其它CPU修改了該Cache line |
F 轉發(Forward) |
Intel提出來的,意思是一個CPU修改資料後,直接針修改的結果轉發給其它CPU |
相關文章
- 快取記憶體一致性協議MESI與記憶體屏障快取記憶體協議
- CPU快取一致性協議MESI,memory barrier和java volatile快取協議Java
- CPU快取和記憶體屏障快取記憶體
- mesi--cpu記憶體一致性協議記憶體協議
- 多核cpu、cpu快取記憶體、快取一致性協議、快取行、記憶體快取記憶體協議
- 快取一致性協議(MESI協議)快取協議
- Java記憶體模型(MESI、記憶體屏障、volatile和鎖及final記憶體語義)Java記憶體模型
- volatile的記憶體屏障的坑記憶體
- CPU快取記憶體快取記憶體
- 簡述偽共享和快取一致性MESI快取
- 記憶體屏障在CPU、JVM、JDK中的實現記憶體JVMJDK
- DDD 和 記憶體快取記憶體快取
- 談談CPU快取記憶體快取記憶體
- C++中的mutable和volatileC++
- 快取一致性協議快取協議
- C++ 中的 volatile 和 atomicC++
- Java中volatile副作用:不使用CPU快取Java快取
- C++記憶體管理:new / delete 和 cookieC++記憶體deleteCookie
- CPU、記憶體、快取的關係詳細解釋!記憶體快取
- C和C++的動態記憶體管理的區別C++記憶體
- CPU快取一致性整理筆記快取筆記
- C/C++程式除錯和記憶體檢測C++除錯記憶體
- CPU和記憶體如何互動的記憶體
- C++獲取記憶體大小和使用率C++記憶體
- 直接記憶體和堆記憶體誰快記憶體
- SUSE檢視CPU和記憶體記憶體
- iOS彙編教程(六)CPU 指令重排與記憶體屏障iOS記憶體
- C++中“記憶體重疊”C++記憶體
- C/C++——C和C++怎樣分配和釋放記憶體,區別是什麼?C++記憶體
- java 記憶體模型-03-快取和重排序Java記憶體模型快取排序
- Cache中的MESI協議基本知識介紹協議
- volatile記憶體可見性和指令重排記憶體
- Web快取 – HTTP協議快取Web快取HTTP協議
- 記憶體可見性和原子性:Synchronized和Volatile的比較記憶體synchronized
- 高併發、低延遲之玩轉CPU快取記憶體(附C#示例)快取記憶體C#
- C++ 繼承中的記憶體佈局C++繼承記憶體
- C++ Builder 5 和 6中VCL的一個記憶體洩漏 BUG (轉)C++UI記憶體
- [C++]記憶體分配C++記憶體