ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

双重检查锁--声名狼藉, 臭名昭著

2022-01-16 21:34:49  阅读:206  来源: 互联网

标签:std thread -- 臭名昭著 声名狼藉 sleep th time sd


双重检查锁模式,是经常听到和用到的方式,既保护了数据的初始化过程,也避免了每次访问时,多个线程要序列化的检查锁问题。 不过,又有观点说,双重检查锁模式是声名狼藉,是臭名昭著的。下面我们通过例子来分析论证。

直接贴代码,附上执行结果,我们先看效果,再做分析。
 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)的风险》 英文版

 

标签:std,thread,--,臭名昭著,声名狼藉,sleep,th,time,sd
来源: https://www.cnblogs.com/azbane/p/15811320.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有