目錄
1. 前言2
2. 結論2
3. volatile應用場景3
4. 內存屏障(Memory Barrier)4
5. setjmp和longjmp4
1) 結果1(非優化編譯:g++ -g -o x x.cpp -O0) 5
2) 結果2(優化編譯:g++ -g -o x x.cpp -O2) 6
6. 不同CPU架構的一致性模型6
7. x86-TSO7
8. C++標准庫對內存順的支持7
1) 頭文件<stdatomic.h> 7
2) 頭文件<atomic> 8
附1:CPU、緩存和主存8
第三級緩存(L3 Cache)多核共享: 8
附2:SMP對稱多處理器結構9
附3:在線C++編譯器9
附4:資源鏈接10
1) C++標准委員會(The C++ Standards Committee) 10
2) 標准C++基金會 10
3) C++之父 10
4) Linux內核關於volatile的說明 10
5) Intel內存模型(Intel Memory Model) 10
6) Intel TSO內存模型 10
7) Sequential Consistency &TSO 10
8) Write buffer 10
9) x86-64和IA-32開發手冊 10
10) 編譯器屏障(Compiler Barriers) 10
11) C ++ 11中memory_order_consume的作用 10
12) MESI(多核CPU緩存一致性協議) 10
13) MESIF(多核CPU緩存一致性協議) 10
1. 前言
本文內容主要針對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)怎么說的(官方鏈接):

2. 結論
1) 與平台無關的多線程程序,volatile幾乎無用(Java和C#中的volatile除外);
2) volatile不保證原子性(一般需使用CPU提供的LOCK指令);
3) volatile不保證執行順序;
4) volatile不提供內存屏障(Memory Barrier)和內存柵欄(Memory Fence);
5) 多核環境中內存的可見性和CPU執行順序不能通過volatile來保障,而是依賴於CPU內存屏障。
注:volatile誕生於單CPU核心時代,為保持兼容,一直只是針對編譯器的,對CPU無影響。
volatile在C/C++中的作用:
1) 告訴編譯器不要將定義的變量優化掉;
2) 告訴編譯器總是從緩存取被修飾的變量的值,而不是寄存器取值。
就前言中的代碼,可移植的實現方式為:
| #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內核源代碼都帶有這些基礎設施。
3. volatile應用場景
1) 信號處理程序;
2) 與硬件打交道(嵌入式開發用得多);
3) 和setjmp、longjmp配合(請參見:http://www.cplusplus.com/reference/csetjmp/setjmp/),原因同信號處理。
4. 內存屏障(Memory Barrier)
內存屏障,也叫內存柵欄(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”,所以無需再操心。
5. setjmp和longjmp
在C/C++中,goto關鍵詞只能函數內的局部跳轉,函數間的跳轉需要使用setjmp和longjmp,這也是有些協程庫基於setjmp和longjmp實現的原因。
1) setjmp
保存上下文,包括信號掩碼,類似於setcontext。該函數的返回值比較特別,第一次返回0,第二次返回的longjmp第二個參數值(如果longjmp第二個參數值為0,則返回值為1,這樣方便區分於第一次返回)。
2) longjmp
該函數從不返回,而是跳回到setjmp保存點,類似於swapcontext。如果沒有先調用setjmp,則longjmp的行為是未定義的。C++代碼可能還會執行棧展開(Unwinding),如果調用了任何非平凡析構函數(non-trivial destructors,需顯示處理的析構函數,如內存釋放),也會導致未定義的行為。
3) 代碼示例(摘自http://www.cplusplus.com/reference/csetjmp/setjmp/)
| /* 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; } } |
上例代碼運行有兩種輸出結果:
1) 結果1(非優化編譯:g++ -g -o x x.cpp -O0)
| X::ctor setjmp return: 0 setjmp return: 19 m: 2018 n: 2018 |
非優先編譯時,總是從內存取值。
2) 結果2(優化編譯:g++ -g -o x x.cpp -O2)
| X::ctor setjmp return: 0 setjmp return: 19 m: -1 n: 2018 |
因m未加volatile修飾,直接讀取寄存器值,因此結果是-1。從這里也可以看出,即使是單線程程序,volatile也是必要的,也說明volatile並不是完全沒用,只是它不能幫助解決多線程的原子性、內存屏障和CPU亂序執行。
另外可發現,上列代碼的類X的析構未執行,但若將exit改成return,則會執行類X的析構,遇到“}”和“return”時,編譯器會安插析構函數調用。
6. 不同CPU架構的一致性模型
注: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內存一致性模型:
1) 順序一致性模型(SC,Sequential Consistency,所有讀取和所有寫入都是有序的);
2) 寬松一致性模型(RC,Relaxed Consistency,允許某些可以重排序),ARM和POWER屬於這類;
3) 弱一致性模型(WC,Weak Consistency,讀取和寫入任意重新排序,僅受顯式內存屏障限制);
4) 完全存儲排序(TSO,Total Store Ordering),SPARC和x86屬於這種類型,只有“store load”一種情況會發生重排序,其它情況和SC模型一樣。
7. x86-TSO
x86-TSO是Intel推出的一種CPU內存一致性模型,特點是只有“Store Load”一種情況會重排序,也就是“Load”可以排(亂序)在“Store”的前面,因此不需要“Load Load”、“Store Store”和“Load Store”這三種屏障。
1) “Store Load”屏障的作用是:確保“前者刷入內存”的數據對“后者加載數據”是可見的;
2) “Load Load”屏障的作用是:確保“前者裝載數據”先於“后者裝載指令”;
3) “Store Store”屏障的作用是:確保“前者數據”先於“后者數據”刷入內存,且“前者刷入內存的數據”對“后者是可見的”;
4) “Load Store”屏障的作用是:確保“前者裝載數據”先於“后者刷新數據到內存”。

8. C++標准庫對內存順的支持
1) 頭文件<stdatomic.h>
| 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 // (讀-修改-寫,默認類型)順序一致性模型,全部順序執行 }; |
2) 頭文件<atomic>
| // 默認內存順類型為“memory_order_seq_cst” std::atomic<bool> std::atomic<int32_t> std::atomic<int64_t> 。。。。。。 |
附1:CPU、緩存和主存
第三級緩存(L3 Cache)多核共享:


附2:SMP對稱多處理器結構
多個CPU對稱工作沒有區別,無主次或從屬關系,平等地訪問內存、外設和一個操作系統,共享全部資源,如總線、內存和I/O系統等,因此也被稱為一致存儲器訪問結構(UMA : Uniform Memory Access)。
其它的架構有:
1) NUMA(Non-Uniform Memory Access,非統一內存訪問),基本特征是將CPU分成多個模型,每個模型多個CPU組成,具有獨立的本地內存和I/O槽口等;
2) MPP(Massive Parallel Processing,海量並行處理結構),基本特征是由多個SMP服務器(每個SMP服務器稱節點)通過節點互聯網絡連接而成,每個節點只訪問自己的本地資源(內存、存儲等),是一種完全無共享(Share Nothing)結構。

附3:在線C++編譯器
1) https://www.tutorialspoint.com/compile_cpp_online.php
2) https://www.jdoodlecom/online-compiler-c++
3) http://coliru.stacked-crooked.com/
4) https://www.onlinegdb.com/online_c++_compiler
7) https://www.cppbuzz.com/compiler/online-c++-compiler
8) https://www.codechef.com/ide/
9) https://repl.it/repls/ZestyNaturalMatch
14) http://www.compileonline.com/
聚合了各種語言在線編譯。
15) https://rextester.com/l/cpp_online_compiler_gcc
還支持其它眾多語言在線編譯。
附4:資源鏈接
1) C++標准委員會(The C++ Standards Committee)
http://www.open-std.org/jtc1/sc22/wg21/
2) 標准C++基金會
3) C++之父
4) Linux內核關於volatile的說明
https://www.kernel.org/doc/html/latest/process/volatile-considered-harmful.html
5) Intel內存模型(Intel Memory Model)
https://en.wikipedia.org/wiki/Intel_Memory_Model
6) Intel TSO內存模型
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)
7) Sequential Consistency &TSO
https://www.cis.upenn.edu/~devietti/classes/cis601-spring2016/sc_tso.pdf
8) Write buffer
https://en.wikipedia.org/wiki/Write_buffer
9) x86-64和IA-32開發手冊
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)
10) 編譯器屏障(Compiler Barriers)
11) C ++ 11中memory_order_consume的作用
https://preshing.com/20140709/the-purpose-of-memory_order_consume-in-cpp11/
12) MESI(多核CPU緩存一致性協議)
https://en.wikipedia.org/wiki/MESI_protocol
13) MESIF(多核CPU緩存一致性協議)
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 |
