内存屏障

来源:互联网 发布:淘宝直播号出售 编辑:程序博客网 时间:2024/04/28 20:03
发信人: 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] [进入讨论区] [返回顶部][分享到搜狐微博腾讯微博新浪微博]
2
发信人: 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] [进入讨论区] [返回顶部][分享到搜狐微博腾讯微博新浪微博]
3
发信人: 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] [进入讨论区] [返回顶部][分享到搜狐微博腾讯微博新浪微博]
4
发信人: 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的值一直都是真。这样,整个逻辑就错了。
原创粉丝点击