C++标准委员会也认为条件竞争的处理很重要。所以,C++标准库提供了 std::once_flag结构体 和 std::call_once() 函数,来处理条件竞争(这种情况的条件竞争:臭名昭著的双重检查锁模式)。
比起锁住互斥量并显示的检查指针,只需要使用 std::call_once() 就可以, 在 std::call_once()函数执行结束时,就能安全的知道指针已经被安全的初始化了。
使用 std::call_once() 会比显示的使用互斥量消耗的资源更少, 特别是当初始化完成之后。
下面我们一起看一个使用例子:
1 xxx.h
2 --------------------------
3
4 #include <iostream>
5 #include <mutex>
6 #include <thread>
7 #include <chrono>
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 "NewStd_Call_Once.h"
4
5 Share_Data * g_sd_var = nullptr;
6 std::mutex g_mtx;
7 std::once_flag init_Flag;
8 void initShareData(){
9 if (!g_sd_var){
10 g_sd_var = new Share_Data;
11
12 //模拟耗时的资源初始化
13 std::chrono::milliseconds sleep_time(500);
14 std::this_thread::sleep_for(sleep_time);
15 g_sd_var->sd_i = 100;
16 std::this_thread::sleep_for(sleep_time);
17 g_sd_var->sd_d = 200.2;
18 std::this_thread::sleep_for(sleep_time);
19 g_sd_var->sd_c = 'A';
20 }
21 }
22 void thread_fun(){
23 std::call_once(init_Flag, initShareData);
24 g_sd_var->printVal(); //后续仅读取访问
25 }
1 main.cpp
2 ---------------------
3 #include "NewStd_Call_Once.h"
4
5 int main(int argc, char *argv[])
6 {
7 QCoreApplication a(argc, argv);
8
9 //! [1] std::call_once 好用,经典~!
10 std::chrono::milliseconds sleep_time(300);
11 std::thread th_a(thread_fun);
12 std::this_thread::sleep_for(sleep_time);
13
14 std::thread th_b(thread_fun);
15 std::this_thread::sleep_for(sleep_time);
16
17 std::thread th_c(thread_fun);
18 std::this_thread::sleep_for(sleep_time);
19
20 std::thread th_d(thread_fun);
21 std::this_thread::sleep_for(sleep_time);
22
23 std::thread th_e(thread_fun);
24 std::this_thread::sleep_for(sleep_time);
25
26 th_a.join();
27 th_b.join();
28 th_c.join();
29 th_d.join();
30 th_e.join();
31 //! [1]
32 return a.exec();
33 }
1 执行输出的结果如下:
2 ------------------------------
3 sd_i:100
4 sd_d:200.2
5 sd_c:A
6 --------------
7 sd_i:100
8 sd_d:200.2
9 sd_c:A
10 --------------
11 sd_i:100
12 sd_d:200.2
13 sd_c:A
14 --------------
15 sd_i:100
16 sd_d:200.2
17 sd_c:A
18 --------------
19 sd_i:100
20 sd_d:200.2
21 sd_c:A
22 --------------
总结:std::call_flag结构体 和 std::call_once()函数,用起来太有爱了,非常棒!
值得注意的是,std::once_flag 和 std::mutex 一样,不可以拷贝和移动。
还有一种初始化过程中潜存着条件竞争:变量被声明为 static 类型, 这种变量在声明后就已经完成初始化; 对于多线程环境中,这就意味着这里有条件竞争---抢着去定义这个变量。
在很多不支持C++标准的编译器上,在实践中,这样的条件竞争是确实存在的,因为在多线程中,每个线程都认为他们是第一个初始化这个变量的线程;
或一个线程对变量进行初始化,而另外一个线程要使用这个变量时,初始化过程还没完成。
在C++11标准中,这些问题都被解决了:初始化及定义完全在一个线程中发生,并且没有其他线程可在初始化完成前对其进行处理,条件竞争终止于初始化阶段,这样比在之后再去处理好的多。
那么,基于此,在只需要一个全局实例情况下,这里提供一个 std::call_once() 的替代方案。
1 class myclass;
2 myclass& get_my_class_instance(){
3 static myclass instance;
4 return instance;
5 }
C++11标准中,多线程可以安全的调用 get_my_class_instance()函数,而不用为数据竞争而担心。
还有一种场景,我们称之为:std::call_once() 作为类的成员的延迟初始化。
原文链接: https://www.cnblogs.com/azbane/p/15811536.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/185998
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!