std::once_flag结构体和std::call_once()函数搭配来处理条件竞争

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

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月12日 上午11:02
下一篇 2023年2月12日 上午11:03

相关推荐