內存屏障

發信人: snnn (cm), 信區: LinuxDev
標 題: 好多人都在問內存屏障啊,我說下我的認識
發信站: 水木社區 (Wed Mar 14 00:21:27 2012), 站內

下面僅說x86平臺。(含amd64)

在gcc中,插入內存屏障有兩種方式:
1.內聯彙編
2. 使用__sync_synchronize 函數。

gcc 4.2及以下版本,__sync_synchronize 這個函數是空實現。這其實是一個BUG。
gcc 4.4及以上版本,__sync_synchronize這個函數對應着mfence這個彙編指令。

4.2的這個BUG的原因在於,x86平臺上,用於實現內存屏障的mfence指令,是否真的有效,是有很大爭議的。

傳統來講,在QPI技術出現之前,也就是說,我們還在用數據總線、地址總線這樣的方式通信的時候,mfence確實是像書上說的那樣,爲我們帶來了強一致性。

intel從nelhalem開始引入點對點(QPI)互聯技術。由於內存和CPU之間的總線沒了,所以,事情有些變化。如果是多CPU,並且沒有用lock指令加鎖,那麼得到是因果一致性。Memory ordering obeys causality (memory ordering respects transitive visibility). 這些信息是通過intel手冊得到的,具體的含義還得諮詢intel的工程師。也就是說,mfence是否還能達到之前我們所預期的效果,是未知的。也就是說,這對JAVA社區是一個極大的災難。

不過這些還不是4.2版本的gcc不實現__sync_synchronize函數的理由,因爲那個時候還沒有nelhalem。感興趣的童鞋請移步 http://gcc.gnu.org/bugzilla/show_bug.cgi?id=36793 (我沒看懂)


與mfence類似的兩個指令就是lfence/sfence。據說這兩個指令實際什麼事情都沒做(在某些型號的CPU上,大概是P4那個時代)。所以mfence可能也是如此。所以我猜是因爲這個,gcc一直無視它,認爲intel不提供類似的指令。


那麼什麼時候需要內存屏障?
當你對順序性有要求,但是又沒有使用lock指令的時候。

gcc的__sync_fetch_and_add這樣的指令,是帶lock prefix的。所以它本身已經是一個內存屏障了。


java中的DCLP問題,我認爲在C++中不存在。
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other functions and members...
}


出synch塊的時候,需要做解鎖操作。在C++中,這個通常是由CAS指令實現的。CAS又帶有lock prefix,那麼就木有問題啊!
JAVA中其實也是一樣。但是寫JDK標準庫的人,需要考慮跨平臺啊,不同的CPU不同的表現啊,等等。


mfence的另外一個問題就是,它是寫給CPU看的。但是編譯器本身也可能會修改代碼原有的執行順序。那麼它能不能讀懂這個指令呢? 如果不能,你在此處用mfence的意義何在?至少,目前沒有哪個編譯器說它能讀懂這個。編譯器能讀懂volatile。編譯器能讀懂pthread_mutex_lock/pthread_mutex_unlock。


以上表述,如你發現有誤,或者懷疑可能有誤,歡迎指出。因爲上述結論基本都是靠讀文檔得到,我並未在實際環境作任何測試。






--
喜歡的生活:睡覺,沒有憂鬱的可以隨時隨地的大笑~

my blog:http://snnn.sinaapp.com/


※ 修改:·snnn 於 Mar 14 00:44:03 2012 修改本文·[FROM: 111.195.74.142]
※ 來源:·水木社區 newsmth.net·[FROM: 111.195.74.142]

發信人: Dieken (風催草低 - 明月何嘗不照人), 信區: LinuxDev
標 題: Re: 好多人都在問內存屏障啊,我說下我的認識
發信站: 水木社區 (Wed Mar 14 01:06:57 2012), 站內

對 DCL 問題的描述,實在齷齪的很:

http://en.wikipedia.org/wiki/Double-checked_locking


【 在 snnn (cm) 的大作中提到: 】
: 下面僅說x86平臺。(含amd64)
: 在gcc中,插入內存屏障有兩種方式:
: 1.內聯彙編
: ...................

--
Debian testing netinst 安裝過程: (1) 下載 netinst iso 安裝,軟件源選擇 debian.cn99.com; (2) 編輯 /etc/default/locale,加入如下三行:
LANG=en_US.UTF-8 LC_CTYPE=zh_CN.UTF-8 LANGUAGE=en_US:en
(3) dpkg-reconfigure locales,把 en_US.ISO8859_1, en_US.UTF-8, zh_CN.GBK, zh_CN.UTF-8 選擇上;
(4) aptitude purge vim-tiny && aptitude install xorg gdm fcitx xfce4 xfonts-wqy ttf-arphic-uming vim-full
(5) 重啓或者用 /etc/init.d/gdm start 進入 X 界面;
(6) 樂意的話可以裝上 msttcorefonts, emacs-intl-fonts, xfonts-intl-chinese。


※ 來源:·水木社區 newsmth.net·[FROM: 114.241.183.*]

發信人: ComeAlong (卡梅隆), 信區: LinuxDev
標 題: Re: 好多人都在問內存屏障啊,我說下我的認識
發信站: 水木社區 (Wed Mar 14 10:53:59 2012), 站內

如果cpu不重排指令, 也不需要硬件內存屏障了, 不過我深切懷疑intel的cpu都沒有做指
令重排的優化. 我個人認爲這種優化意義有限.



【 在 snnn (cm) 的大作中提到: 】
: 標 題: 好多人都在問內存屏障啊,我說下我的認識
: 發信站: 水木社區 (Wed Mar 14 00:21:27 2012), 站內
:
: 下面僅說x86平臺。(含amd64)
:
: 在gcc中,插入內存屏障有兩種方式:
: 1.內聯彙編
: 2. 使用__sync_synchronize 函數。
:
: gcc 4.2及以下版本,__sync_synchronize 這個函數是空實現。這其實是一個BUG。
: gcc 4.4及以上版本,__sync_synchronize這個函數對應着mfence這個彙編指令。
:
: 4.2的這個BUG的原因在於,x86平臺上,用於實現內存屏障的mfence指令,是否真的有效,是有很大爭議的。
:
: 傳統來講,在QPI技術出現之前,也就是說,我們還在用數據總線、地址總線這樣的方式通信的時候,mfence確實是像書上說的那樣,爲我們帶來了強一致性。
:
: intel從nelhalem開始引入點對點(QPI)互聯技術。由於內存和CPU之間的總線沒了,所以,事情有些變化。如果是多CPU,並且沒有用lock指令加鎖,那麼得到是因果一致性。Memory ordering obeys causality (memory ordering respects transitive visibility). 這些信息是通過intel手冊得到的,具體的含義還得諮詢intel的工程師。也就是說,mfence是否還能達到之前我們所預期的效果,是未知的。也就是說,這對JAVA社區是一個極大的災難。
:
: 不過這些還不是4.2版本的gcc不實現__sync_synchronize函數的理由,因爲那個時候還沒有nelhalem。感興趣的童鞋請移步 http://gcc.gnu.org/bugzilla/show_bug.cgi?id=36793 (我沒看懂)
:
:
: 與mfence類似的兩個指令就是lfence/sfence。據說這兩個指令實際什麼事情都沒做(在某些型號的CPU上,大概是P4那個時代)。所以mfence可能也是如此。所以我猜是因爲這個,gcc一直無視它,認爲intel不提供類似的指令。
:
:
: 那麼什麼時候需要內存屏障?
: 當你對順序性有要求,但是又沒有使用lock指令的時候。
:
: gcc的__sync_fetch_and_add這樣的指令,是帶lock prefix的。所以它本身已經是一個內存屏障了。
:
:
: java中的DCLP問題,我認爲在C++中不存在。
: // Broken multithreaded version
: // "Double-Checked Locking" idiom
: class Foo {
: private Helper helper = null;
: public Helper getHelper() {
: if (helper == null)
: synchronized(this) {
: if (helper == null)
: helper = new Helper();
: }
: return helper;
: }
: // other functions and members...
: }
:
:
: 出synch塊的時候,需要做解鎖操作。在C++中,這個通常是由CAS指令實現的。CAS又帶有lock prefix,那麼就木有問題啊!
: JAVA中其實也是一樣。但是寫JDK標準庫的人,需要考慮跨平臺啊,不同的CPU不同的表現啊,等等。
:
:
: mfence的另外一個問題就是,它是寫給CPU看的。但是編譯器本身也可能會修改代碼原有的執行順序。那麼它能不能讀懂這個指令呢? 如果不能,你在此處用mfence的意義何在?至少,目前沒有哪個編譯器說它能讀懂這個。編譯器能讀懂volatile。編譯器能讀懂pthread_mutex_lock/pthread_mutex_unlock。
:
:
: 以上表述,如你發現有誤,或者懷疑可能有誤,歡迎指出。因爲上述結論基本都是靠讀文檔得到,我並未在實際環境作任何測試。
:
:
:
:
:
:
: --
: 喜歡的生活:睡覺,沒有憂鬱的可以隨時隨地的大笑~
:
: my blog:http://snnn.sinaapp.com/
:
:
: ※ 修改:·snnn 於 Mar 14 00:44:03 2012 修改本文·[FROM: 111.195.74.142]
: ※ 來源:·水木社區 newsmth.net·[FROM: 111.195.74.142]


--
什麼罕物,連人之高低不擇, 還說`通靈'不`通靈'呢!我也不要這勞什子了!
家裏姐姐妹妹都沒有,單我有,我說沒趣,如今來了這們一個神仙似的妹妹也
沒有,可知這不是個好東西.


※ 來源:·水木社區 newsmth.net·[FROM: 116.228.169.*]

發信人: abadcafe (abadcafe), 信區: LinuxDev
標 題: Re: 好多人都在問內存屏障啊,我說下我的認識
發信站: 水木社區 (Wed Mar 14 11:05:09 2012), 站內

我也搭車說說我對volatile的理解,以及爲什麼多線程程序需要這個關鍵字吧。不對的地方還請樓主指教。

我們先寫一個C代碼:

#include <pthread.h>
volatile int a = 1;
int b = 2;

void * thread(void * arg)
{
a = 0;
b = 0;
}

int main(void)
{
int i = 0;
pthread_t thread_id;

pthread_create(&thread_id, NULL, thread, NULL);

while (a) i++;
while (b) i++;

printf("%d %d %d\n", a, b, i);

return 0;
}

用armgcc -O2編譯,再把它反彙編之(我對arm彙編比較熟悉):

int main(void)
{
84f4: e92d4010 stmdb sp!, {r4, lr}
int i = 0;
84f8: e3a04000 mov r4, #0 ; 0x0
pthread_t thread_id;

pthread_create(&thread_id, NULL, thread, NULL);
84fc: e1a01004 mov r1, r4
8500: e59f2068 ldr r2, [pc, #104] ; 8570 <.text+0x178>
8504: e1a03004 mov r3, r4
8508: e24dd004 sub sp, sp, #4 ; 0x4
850c: e1a0000d mov r0, sp
8510: ebffffaf bl 83d4 <.text-0x24>

while (a) i++;
8514: e59f1058 ldr r1, [pc, #88] ; 8574 <.text+0x17c>
8518: e5913000 ldr r3, [r1]
851c: e1530004 cmp r3, r4
8520: e1a02001 mov r2, r1
8524: 0a000003 beq 8538 <main+0x44>
8528: e5923000 ldr r3, [r2]
852c: e2844001 add r4, r4, #1 ; 0x1
8530: e3530000 cmp r3, #0 ; 0x0
8534: 1afffffb bne 8528 <main+0x34>
while (b) i++;
8538: e59f2038 ldr r2, [pc, #56] ; 8578 <.text+0x180>
853c: e5923000 ldr r3, [r2]
8540: e3530000 cmp r3, #0 ; 0x0
8544: 0a000001 beq 8550 <main+0x5c>
8548: e2844001 add r4, r4, #1 ; 0x1
854c: 1afffffd bne 8548 <main+0x54>

printf("%d %d %d\n", a, b, i);
8550: e5911000 ldr r1, [r1]
8554: e5922000 ldr r2, [r2]
8558: e1a03004 mov r3, r4
855c: e59f0018 ldr r0, [pc, #24] ; 857c <.text+0x184>
8560: ebffff98 bl 83c8 <.text-0x30>

return 0;
}

我們可以看到8534和854c的兩處bne指令的跳轉位置明顯不一樣:8534跳到了8528處,會重新從內存中load變量a,於是可以正確地退出,而854c則由編譯器做了假設,假設變量b的值一直都是真。這樣,整個邏輯就錯了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章