双重检查锁模式,是经常听到和用到的方式,既保护了数据的初始化过程,也避免了每次访问时,多个线程要序列化的检查锁问题。 不过,又有观点说,双重检查锁模式是声名狼藉,是臭名昭著的。下面我们通过例子来分析论证。直接贴代码,附上执行结果,我们先看效果,再做分析。
1 xxx.h
2 ----------------------------
3 #include <iostream>
4 #include <mutex>
5 #include <thread>
6 #include <chrono>
7
8
9 //! [0] C风格:面向过程的双重检查锁
10 //share data
11 struct Share_Data{
12 int sd_i;
13 double sd_d;
14 char sd_c;
15
16 std::mutex prt_mtx;
17 void printVal(){
18
19 std::lock_guard<std::mutex> lkgd(prt_mtx);
20 std::cout<<"sd_i:"<<sd_i<<std::endl;
21 std::cout<<"sd_d:"<<sd_d<<std::endl;
22 std::cout<<"sd_c:"<<sd_c<<std::endl;
23 std::cout<<"--------------"<<std::endl;
24 }
25 };
26
27 extern Share_Data * g_sd_var;
28 extern std::mutex g_mtx;
29 extern void thread_fun();
30 //! [0]
1 xxx.cpp
2 --------------------
3 #include "Double_Checked_Lock.h"
4
5 Share_Data * g_sd_var = nullptr;
6 std::mutex g_mtx;
7
8 void thread_fun(){
9 if (!g_sd_var){
10 std::lock_guard<std::mutex> lkgd(g_mtx);
11 if (!g_sd_var){
12 g_sd_var = new Share_Data;
13
14 //模拟耗时的资源初始化
15 std::chrono::milliseconds sleep_time(500);
16 std::this_thread::sleep_for(sleep_time);
17 g_sd_var->sd_i = 100;
18 std::this_thread::sleep_for(sleep_time);
19 g_sd_var->sd_d = 200.2;
20 std::this_thread::sleep_for(sleep_time);
21 g_sd_var->sd_c = 'A';
22 }
23 }
24 g_sd_var->printVal(); //后续仅读取访问
25 }
1 main.cpp
2 -------------------------------
3 #include "Double_Checked_Lock.h"
4 int main(int argc, char *argv[])
5 {
6 QCoreApplication a(argc, argv);
7
8 std::chrono::milliseconds sleep_time(300);
9 std::thread th_a(thread_fun);
10 std::this_thread::sleep_for(sleep_time);
11
12 std::thread th_b(thread_fun);
13 std::this_thread::sleep_for(sleep_time);
14
15 std::thread th_c(thread_fun);
16 std::this_thread::sleep_for(sleep_time);
17
18 std::thread th_d(thread_fun);
19 std::this_thread::sleep_for(sleep_time);
20
21 std::thread th_e(thread_fun);
22 std::this_thread::sleep_for(sleep_time);
23
24 th_a.join();
25 th_b.join();
26 th_c.join();
27 th_d.join();
28 th_e.join();
29 return a.exec();
30 }
1 执行输出的结果如下:
2 ------------------------------
3 sd_i:-842150451
4 sd_d:-6.27744e+66
5 sd_c:
6 --------------
7 sd_i:100
8 sd_d:-6.27744e+66
9 sd_c:
10 --------------
11 sd_i:100
12 sd_d:-6.27744e+66
13 sd_c:
14 --------------
15 sd_i:100
16 sd_d:200.2
17 sd_c:
18 --------------
19 sd_i:100
20 sd_d:200.2
21 sd_c:A
22 --------------
总结:惊不惊喜,意不意外,哈哈哈。想要的结果是每个线程都输出100;200.2;A;实际上却不是。以后不要使用“双重检查锁模式”咯,它是臭名昭著的!下面我们来分析一下,错哪里了,导致双重锁检查声名狼藉。这个模式为什么声明狼藉呢? 因为这里存在潜在的条件竞争。未被锁保护的读取操作(第一次检查)没有与其他线程里被锁保护的写入操作(第二次检查后的初始化过程)进行同步,因此就会产生条件竞争。这个条件竞争不仅覆盖指针本身,还会影响到其指向的对象; 即使一个线程知道另一个线程完成对指针进行写入,它可能没有看到新创建的对象实例,然后调用读取操作接口,就会得到不正确的结果。这个例子是一种典型的条件竞争-----数据竞争,C++标准中这会被指定为 “未定义行为” 。可以参考,著名的《C++和双重检查锁定模式(DCLP)的风险》 英文版。
原文链接: https://www.cnblogs.com/azbane/p/15811320.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/186120
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!