免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: zylthinking
打印 上一主题 下一主题

为什么用户空间程序不用考虑内存屏障? [复制链接]

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
61 [报告]
发表于 2011-10-19 15:44 |只看该作者
本帖最后由 zylthinking 于 2011-10-19 15:51 编辑
O3写出问题,那都是因为该加volatile的地方不加,O3本身应该是符合单线程的语意而没有问题的.很多人 ...
cjaizss 发表于 2011-10-19 15:26


你应该抱着虚心的态度, 仔细琢磨请教, 就算不好意思认错, 也不妨将相关帖子及C版给你发的链接上面的文字好歹读一遍, 已经连续告诉你错了至少2次, 还是一副教训人的模样, 懒得再和你说

论坛徽章:
3
2015年迎新春徽章
日期:2015-03-04 09:56:11数据库技术版块每日发帖之星
日期:2016-08-03 06:20:00数据库技术版块每日发帖之星
日期:2016-08-04 06:20:00
62 [报告]
发表于 2011-10-19 15:57 |只看该作者
你应该抱着虚心的态度, 仔细琢磨请教, 连续告诉你错了, 还是一副教训人的模样, 懒得再和你说
zylthinking 发表于 2011-10-19 15:44



    算了,我再说一句吧,volatile是硬件时代产生的关键字.
    gcc的:::memory来做内存传输屏蔽,与其说是来做屏蔽,倒不如说是gcc的BUG.
    另外,为什么编译的时候倒序,完全是基于内存的先后写顺序无所谓的认为.而已经牵涉到了函数,不过再仔细看了看你的1楼帖子,再想了想,可能的确会导致这样的行为发生了.
    但
   A();
    B();
    是不会乱作
   B();
    A();
    不过呢,前面你例子里的代码应该作为一个整体,应当用锁隔开才是多线程应用中的正道.

论坛徽章:
3
2015年迎新春徽章
日期:2015-03-04 09:56:11数据库技术版块每日发帖之星
日期:2016-08-03 06:20:00数据库技术版块每日发帖之星
日期:2016-08-04 06:20:00
63 [报告]
发表于 2011-10-19 15:58 |只看该作者
算了,我再说一句吧,volatile是硬件时代产生的关键字.
    gcc的:::memory来做内存传输屏蔽,与其 ...
cjaizss 发表于 2011-10-19 15:57



    不过话说回来,我又不是很相信1楼的代码会出现颠倒.
    否则
   lock();
     x=1;
     unlock();
    凭什么不能优化成
    x=1;
   lock();
     unlock();

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
64 [报告]
发表于 2011-10-19 16:08 |只看该作者
算了,我再说一句吧,volatile是硬件时代产生的关键字.
    gcc的:::memory来做内存传输屏蔽,与其 ...
cjaizss 发表于 2011-10-19 15:57


简直本末导致, malloc 不过直接随手打的, 现在成了你的关注要点了,
那么好, char* p = malloc(); ptr->buf = p; ptr->enable = true; 你还又什么话说???
我还没说 ptr->buf = malloc() 产生乱序的是 mov eax, ptr->buf 呢(我没有把握在这里一定产生乱序);

还说什么volatile是硬件时代产生的关键字, 我就不明白什么叫硬件时代了, 现在莫非是软件时代???
如果说 volatile 语意能影响硬件才通顺, 问题是你自己说的, 链接就不给了, 大概意思是 volatile 影响的是编译器, 使其不改变二进制文件中指令级先后顺序。 所以我说如果你这句话如果是对的, 那说明你所谓的volatile能防乱序就错了, 看的懂我在说什么吗??? 去我贴出那个链接里面看过那文章吗????

固步自封, 自以为是。 如果你能拿出证据说明我是错的, 我早认识到了, 拿着已经被我认为是错的观点一次又一次重复, 还自我感觉良好???

论坛徽章:
0
65 [报告]
发表于 2011-10-19 16:12 |只看该作者
回复 63# cjaizss


   
    O3写出问题,那都是因为该加volatile的地方不加,O3本身应该是符合单线程的语意而没有问题的.很多人写多线程应用程序的时候需要加volatile的地方不加volatile,我一直很奇怪

    对此很感兴趣,多线程程序在什么情况下应该加volatile呢?能举个例子么?

   
   lock();
     x=1;
     unlock();
    凭什么不能优化成
    x=1;
   lock();
     unlock();

    因为锁本身就有屏障作用。

论坛徽章:
3
2015年迎新春徽章
日期:2015-03-04 09:56:11数据库技术版块每日发帖之星
日期:2016-08-03 06:20:00数据库技术版块每日发帖之星
日期:2016-08-04 06:20:00
66 [报告]
发表于 2011-10-19 16:13 |只看该作者
回复  cjaizss


   
    对此很感兴趣,多线程程序在什么情况下应该加volatile呢?能举个例子么?
...
kouu 发表于 2011-10-19 16:12



    我的意思是说锁本身也是一个函数,编译器编译的时候没责任知道那是锁.

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
67 [报告]
发表于 2011-10-19 16:14 |只看该作者
不过话说回来,我又不是很相信1楼的代码会出现颠倒.
    否则
   lock();
     x=1;
     unl ...
cjaizss 发表于 2011-10-19 15:58


自己去琢磨, 知道也不和你说了

论坛徽章:
3
2015年迎新春徽章
日期:2015-03-04 09:56:11数据库技术版块每日发帖之星
日期:2016-08-03 06:20:00数据库技术版块每日发帖之星
日期:2016-08-04 06:20:00
68 [报告]
发表于 2011-10-19 16:16 |只看该作者
本帖最后由 cjaizss 于 2011-10-19 16:23 编辑
简直本末导致, malloc 不过直接随手打的, 现在成了你的关注要点了,
那么好, char* p = malloc(); ...
zylthinking 发表于 2011-10-19 16:08



    我的意思是说,有那么一段时间,时序的要求几乎完全靠这个volatile约束
   更何况,现在还有一大堆编译器也只好靠这个volatile来约束行为
   也就是原则上本应该带上volatile的,其操作顺序不应该被强制改变
   我再说的更明确一点,不要把补丁当正统

论坛徽章:
11
未羊
日期:2013-12-16 12:45:4615-16赛季CBA联赛之青岛
日期:2016-04-11 19:17:4715-16赛季CBA联赛之广夏
日期:2016-04-06 16:34:012015亚冠之卡尔希纳萨夫
日期:2015-11-10 10:04:522015亚冠之大阪钢巴
日期:2015-07-30 18:29:402015亚冠之城南
日期:2015-06-15 17:56:392015亚冠之卡尔希纳萨夫
日期:2015-05-15 15:19:272015亚冠之山东鲁能
日期:2015-05-14 12:38:13金牛座
日期:2014-12-04 15:34:06子鼠
日期:2014-10-16 13:40:4715-16赛季CBA联赛之八一
日期:2016-07-22 09:41:40
69 [报告]
发表于 2011-10-19 16:19 |只看该作者
本帖最后由 zylthinking 于 2011-10-19 16:22 编辑
回复  cjaizss


   
    对此很感兴趣,多线程程序在什么情况下应该加volatile呢?能举个例子么?
...
kouu 发表于 2011-10-19 16:12



volatile 昨天我查了部分资料, 琢磨了一个理解, 在 http://bbs.chinaunix.net/redirec ... 38&ptid=3606534 及其以后的几个帖子, 总的意思是, volatile 起的和 barrier() 差不多的作用, 只是这个更限制为 编译器保证不修改若干个 volatile 变量之间的写(不知道有没有读) 的先后顺序,

也就是说 volatile int a = 1; volatile int b = 2; c = 3; 编译器保证 a 赋值在 b前, 但我不知道编译器报不保证 c在他们之后

论坛徽章:
0
70 [报告]
发表于 2011-10-19 16:20 |只看该作者
volatile 昨天我查了部分资料, 琢磨了一个理解, 在  及其以后的几个帖子, 总的意思是, volatile  ...
zylthinking 发表于 2011-10-19 16:19



    对于volatile我觉得能不用就不用。(尤其是用户态,用了volatile基本都是代表有错的)


我觉得volatile变量是一个有争议的问题。举个例子:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int *p;
void *func(void * arg) {
  sleep(5);
  *p = 1;
  return NULL;
}

void wait() {
    int flag=0;  //volatile ?
    int var = 2;
    p = &var + 1; //point address of flag,直接操作flag地址的话就阻止了编译器优化
    while ( flag == 0 )
        sleep(1);
   printf("wait end\n");
}

int main(int argc, char **argv) {
  pthread_t tid;
  pthread_create(&tid, NULL, &func, NULL);
  wait();
  return 0;
}
通过gcc test.c -pthread -O3优化编译,while(flag==0)被优化成下面的死循环,wait一直等下去了。
8048525:       c7 04 24 01 00 00 00    movl   $0x1,(%esp)
804852c:       e8 ff fe ff ff            call   8048430 <sleep@plt>
8048531:       eb f2                 jmp    8048525 <wait+0x31>
但是如果修改flag = 0 成volatile flag = 0的话。则是正常的判断代码,wait可以成功结束。
8048533:       75 16                   jne    804854b <wait+0x57>
8048535:       c7 04 24 01 00 00 00    movl   $0x1,(%esp)
804853c:       e8 ef fe ff ff          call   8048430 <sleep@plt>
8048541:       8b 45 f4                mov    -0xc(%ebp),%eax
8048544:       3d 88 08 00 00          cmp    $0x888,%eax
8048549:       74 ea                   je     8048535 <wait+0x41>

注意:为了方便,上面的例子是不符合规范的,因为实际上在多线程隐藏的访问了共享变量,但是没有使用锁保护。不过即使使用了锁,也不能阻止编译器把wait优化成死循环。

貌似volatile起到作用了,但是我要说这里用volatile是“错”的,虽然它的结果是正确的。实际上由于代码的bug导致了必须使用volatile才能达到正确的结果,这里就是p = &var +1这句话(还有一些汇编指令),语法没有问题,但是可能导致隐藏的问题:一个它会让编译器错误的优化,这样的指针如果是非法的或者在其他线程没有保护的访问将会产生严重的错误,而且难以发现。所以可以说正是由于这段错误的代码才造就了volatile,在编写代码过程中应该避免使用volatile,如果有volatile可能就是代码的bug。不过内核中有一些用到volatile的地方需要特别注意下。顺便贴一段中文的内核帮助文档。
volatile-considered-harmful.txt
为什么不应该使用“volatile”类型
------------------------------

C程序员通常认为volatile表示某个变量可以在当前执行的线程之外被改变;因此,在内核
中用到共享数据结构时,常常会有C程序员喜欢使用volatile这类变量。换句话说,他们经
常会把volatile类型看成某种简易的原子变量,当然它们不是。在内核中使用volatile几
乎总是错误的;本文档将解释为什么这样。

理解volatile的关键是知道它的目的是用来消除优化,实际上很少有人真正需要这样的应
用。在内核中,程序员必须防止意外的并发访问破坏共享的数据结构,这其实是一个完全
不同的任务。用来防止意外并发访问的保护措施,可以更加高效的避免大多数优化相关的
问题。

像volatile一样,内核提供了很多原语来保证并发访问时的数据安全(自旋锁, 互斥量,内
存屏障等等),同样可以防止意外的优化。如果可以正确使用这些内核原语,那么就没有
必要再使用volatile。如果仍然必须使用volatile,那么几乎可以肯定在代码的某处有一
个bug。在正确设计的内核代码中,volatile能带来的仅仅是使事情变慢。

思考一下这段典型的内核代码:

    spin_lock(&the_lock);
    do_something_on(&shared_data);
    do_something_else_with(&shared_data);
    spin_unlock(&the_lock);

如果所有的代码都遵循加锁规则,当持有the_lock的时候,不可能意外的改变shared_data的值。任何可能访问该数据的其他代码都会在这个锁上等待。自旋锁原语跟内存屏障一— 它
们显式的用来书写成这样 —— 意味着数据访问不会跨越它们而被优化。所以本来编译器认为它知道在shared_data里面将有什么,但是因为spin_lock()调用跟内存屏障一样,会强制编译器忘记它所知道的一切。那么在访问这些数据时不会有优化的问题。

如果shared_data被声名为volatile,锁操作将仍然是必须的。就算我们知道没有其他人正在
使用它,编译器也将被阻止优化对临界区内shared_data的访问。在锁有效的同时,
shared_data不是volatile的。在处理共享数据的时候,适当的锁操作可以不再需要
volatile —— 并且是有潜在危害的。

volatile的存储类型最初是为那些内存映射的I/O寄存器而定义。在内核里,寄存器访问也应该被锁保护,但是人们也不希望编译器“优化”临界区内的寄存器访问。内核里I/O的内存访问是通过访问函数完成的;不赞成通过指针对I/O内存的直接访问,并且不是在所有体系架构上都能工作。那些访问函数正是为了防止意外优化而写的,因此,再说一次,volatile类型不是必需的。

另一种引起用户可能使用volatile的情况是当处理器正忙着等待一个变量的值。正确执行一
个忙等待的方法是:

    while (my_variable != what_i_want)
        cpu_relax();

cpu_relax()调用会降低CPU的能量消耗或者让位于超线程双处理器;它也作为内存屏障一样出现,所以,再一次,volatile不是必需的。当然,忙等待一开始就是一种反常规的做法。

在内核中,一些稀少的情况下volatile仍然是有意义的:在一些体系架构的系统上,允许直接的I/0内存访问,那么前面提到的访问函数可以使用volatile。基本上,每一个访问函数调用它自己都是一个小的临界区域并且保证了按照程序员期望的那样发生访问操作。某些会改变内存的内联汇编代码虽然没有什么其他明显的附作用,但是有被GCC删除的可能性。在汇编声明中加上volatile关键字可以防止这种删除操作-
Jiffies变量是一种特殊情况,虽然每次引用它的时候都可以有不同的值,但读jiffies变量时不需要任何特殊的加锁保护。所以jiffies变量可以使用volatile,但是不赞成其他跟jiffies相同类型变量使用volatile。Jiffies被认为是一种“愚蠢的遗留物"(Linus的话)因为解决这个问题比保持现状要麻烦的多。
由于某些I/0设备可能会修改连续一致的内存,所以有时,指向连续一致内存的数据结构的指针需要正确的使用volatile。网络适配器使用的环状缓存区正是这类情形的一个例子,其中适配器用改变指针来表示哪些描述符已经处理过了。
对于大多代码,上述几种可以使用volatile的情况都不适用。所以,使用volatile是一种bug并且需要对这样的代码额外仔细检查。那些试图使用volatile的开发人员需要退一步想想他们真正想实现的是什么。
非常欢迎删除volatile变量的补丁 - 只要证明这些补丁完整的考虑了并发问题。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP