• 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() 作为类的成员的延迟初始化。

  • 相关阅读:
    LTE
    LTE
    LTE
    LTE
    LTE DL-SCH and PDSCH Processing Chain
    LTE PDSCH Port 5 UE-Specific Beamforming
    推荐我的公众号
    GitHub Top 微信小程序
    深度前馈网络
    考研经历吐血总结
  • 原文地址:https://www.cnblogs.com/azbane/p/15811536.html
Copyright © 2020-2023  润新知