BBYR Achieve
返回信息流
这是一条镜像帖。来源:北邮人论坛 / cpp / #81455同步于 2014/8/11
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖

[多线程毁三观]3.都double check了还是错的。

nuanyangyang
2014/8/11镜像同步20 回复
下面的程序想实现lazy initialisation,即,一个对象在第一次访问的时候创建。这个模式叫做double check locking,经常用,可惜也是错的。 #include <mutex> class Foo {}; Foo *f; // 初始为nullptr std::mutex f_lock; Foo *get_foo() { if (f == nullptr) { // 检查一下f是不是已经初始化了,如果是就直接返回 f_lock.lock(); // 锁一下,以防两个线程一起初始化 if (f == nullptr) { // 再次检查,有可能两个线程一起加了锁,但一个已经成功地初始化了 f = new Foo(); // 真的创建对象 } f_lock.unlock(); // 解锁 } return f; } 在现实中,有可能会看到返回的地址是有效的,但这个Foo对象内部的初始化还没有完。这是什么原因呢? 原因是一个线程写f = new Foo()的时候,另一个线程无视锁,进行了一次对f的读取,就是第一个f==NULL。注意到这次读是在f_lock.lock()以外的,它并不受锁的保护。本质上,这次读和另一个线程的f=new Foo()的写不存在happen-before关系,属于data race。这是未定义行为,从什么也不发生到计算机冒烟都是允许的。 解决方法,可以让锁保护整个函数,也可以把f变成atomic变量。但是最简单的办法是利用C++11的static的语义。下面的例子摘自维基百科 http://en.wikipedia.org/wiki/Double-checked_locking static Foo* instance() { static Foo f; return &f; }
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
q397273499机器人#1 · 2014/8/11
学习了 通过『我邮2.0』发布
gaoweiwei机器人#2 · 2014/8/11
返回的地址是有效的,但这个Foo对象内部的初始化还没有完?什么意思?是指真的创建对象的线程在Foo初始化完之前就对f进行了写?
nuanyangyang机器人#3 · 2014/8/11
【 在 gaoweiwei 的大作中提到: 】 : 返回的地址是有效的,但这个Foo对象内部的初始化还没有完?什么意思?是指真的创建对象的线程在Foo初始化完之前就对f进行了写? 比如这段程序 class Foo { public: int x; public Foo() { x = 42; } }; Foo *f; // 初始化为nullptr void fun() { f = new Foo(); } f = new Foo()这一个表达式涉及3个操作: 1. 分配Foo占的空间 2. 把这个Foo对象的x成员的值赋为42 3. 把这个Foo对象本身的地址赋值给局部变量f。 如果这时候有第二个线程在跑,那么第二个线程有可能观察到这样的结果: 1发生了,即内存已经分配了; 3发生了,即f确实指向这块空间; 但是2往内存里写的42还在另一个cpu的缓存里,还没来得及让第二个线程看到。 于是第二个线程虽然看到了f!=nullptr,但是如果试图查看f->x,看到的可能是垃圾值。
liupc123123机器人#4 · 2014/8/11
指令重排?和JAVA中的单例模式的double check差不多吧
nuanyangyang机器人#5 · 2014/8/11
【 在 liupc123123 的大作中提到: 】 : 指令重排?和JAVA中的单例模式的double check差不多吧 嗯。是一回事。选自维基百科,原来的例子是Java的。
gaoweiwei机器人#6 · 2014/8/11
跟你上两个帖子的情况类似啊 【 在 nuanyangyang 的大作中提到: 】 : 比如这段程序 : [code=cpp] : class Foo { : ...................
nuanyangyang机器人#7 · 2014/8/11
【 在 gaoweiwei 的大作中提到: 】 : 跟你上两个帖子的情况类似啊 嗯,都是有意无意地造成data race
iFadeToBlack机器人#8 · 2014/8/11
这个应该属于re-order的范围:compiler re-ordering 和 OoO execution compiler re-ordering 还可能造成的一个后果是,两个check都跑到lock外面了,导致多个实例被创建 这也就是所谓的 double-checked locking is broken: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html 其实吧,真正的原因是对于没有定义完备memory model的语言,multi-threading 是不能通过 library 来实现的。
nuanyangyang机器人#9 · 2014/8/11
【 在 iFadeToBlack 的大作中提到: 】 : 这个应该属于re-order的范围:compiler re-ordering 和 OoO execution : compiler re-ordering 还可能造成的一个后果是,两个check都跑到lock外面了,导致多个实例被创建 : 这也就是所谓的 double-checked locking is broken: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html : ................... 就是看了Hans Boehm的论文Threads Cannot be Implemented as a Library才来吐槽的。