基于该分析llvm的libc++,代替gun的libstdc++,由于libstdc++的代码里太多宏了,看起来蛋疼。

在多线程编程中,有一个常见的情景是某个任务仅仅须要运行一次。在C++11中提供了非常方便的辅助类once_flag,call_once。

声明

首先来看一下once_flag和call_once的声明:

  1. struct once_flag
  2. {
  3. constexpr once_flag() noexcept;
  4. once_flag(const once_flag&) = delete;
  5. once_flag& operator=(const once_flag&) = delete;
  6. };
  7. template<class Callable, class ...Args>
  8. void call_once(once_flag& flag, Callable&& func, Args&&... args);
  9.  
  10. } // std

能够看到once_flag是不同意改动的。拷贝构造函数和operator=函数都声明为delete,这样防止程序猿乱用。

另外,call_once也是非常easy的。仅仅要传进一个once_flag。回调函数,和參数列表就能够了。

演示样例

看一个演示样例:

http://en.cppreference.com/w/cpp/thread/call_once

  1. #include <iostream>
  2. #include <thread>
  3. #include <mutex>
  4.  
  5. std::once_flag flag;
  6.  
  7. void do_once()
  8. {
  9. std::call_once(flag, [](){ std::cout << "Called once" << std::endl; });
  10. }
  11.  
  12. int main()
  13. {
  14. std::thread t1(do_once);
  15. std::thread t2(do_once);
  16. std::thread t3(do_once);
  17. std::thread t4(do_once);
  18.  
  19. t1.join();
  20. t2.join();
  21. t3.join();
  22. t4.join();
  23. }

保存为main.cpp,假设是用g++或者clang++来编绎:

g++ -std=c++11 -pthread main.cpp

clang++ -std=c++11 -pthread main.cpp

./a.out

能够看到,仅仅会输出一行

Called once

值得注意的是,假设在函数运行中抛出了异常,那么会有还有一个在once_flag上等待的线程会运行。

比方以下的样例:

  1. #include <iostream>
  2. #include <thread>
  3. #include <mutex>
  4.  
  5. std::once_flag flag;
  6.  
  7. inline void may_throw_function(bool do_throw)
  8. {
  9. // only one instance of this function can be run simultaneously
  10. if (do_throw) {
  11. std::cout << "throw\n"; // this message may be printed from 0 to 3 times
  12. // if function exits via exception, another function selected
  13. throw std::exception();
  14. }
  15.  
  16. std::cout << "once\n"; // printed exactly once, it's guaranteed that
  17. // there are no messages after it
  18. }
  19.  
  20. inline void do_once(bool do_throw)
  21. {
  22. try {
  23. std::call_once(flag, may_throw_function, do_throw);
  24. }
  25. catch (...) {
  26. }
  27. }
  28.  
  29. int main()
  30. {
  31. std::thread t1(do_once, true);
  32. std::thread t2(do_once, true);
  33. std::thread t3(do_once, false);
  34. std::thread t4(do_once, true);
  35.  
  36. t1.join();
  37. t2.join();
  38. t3.join();
  39. t4.join();
  40. }

输出的结果可能是0到3行throw。和一行once。

实际上once_flag相当于一个锁,使用它的线程都会在上面等待。仅仅有一个线程同意运行。假设该线程抛出异常,那么从等待中的线程中选择一个。反复上面的流程。

实现分析

once_flag实际上仅仅有一个unsigned long __state_的成员变量,把call_once声明为友元函数。这样call_once能改动__state__变量:

  1. struct once_flag
  2. {
  3. once_flag() _NOEXCEPT : __state_(0) {}
  4. private:
  5. once_flag(const once_flag&); // = delete;
  6. once_flag& operator=(const once_flag&); // = delete;
  7.  
  8. unsigned long __state_;
  9.  
  10. template<class _Callable>
  11. friend void call_once(once_flag&, _Callable);
  12. };

call_once则用了一个__call_once_param类来包装函数,非经常见的模板编程技巧。

  1. template <class _Fp>
  2. class __call_once_param
  3. {
  4. _Fp __f_;
  5. public:
  6. explicit __call_once_param(const _Fp& __f) : __f_(__f) {}
  7. void operator()()
  8. {
  9. __f_();
  10. }
  11. };
  12. template<class _Callable>
  13. void call_once(once_flag& __flag, _Callable __func)
  14. {
  15. if (__flag.__state_ != ~0ul)
  16. {
  17. __call_once_param<_Callable> __p(__func);
  18. __call_once(__flag.__state_, &__p, &__call_once_proxy<_Callable>);
  19. }
  20. }

最重要的是__call_once函数的实现:

  1. static pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
  2. static pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
  3.  
  4. void
  5. __call_once(volatile unsigned long& flag, void* arg, void(*func)(void*))
  6. {
  7. pthread_mutex_lock(&mut);
  8. while (flag == 1)
  9. pthread_cond_wait(&cv, &mut);
  10. if (flag == 0)
  11. {
  12. #ifndef _LIBCPP_NO_EXCEPTIONS
  13. try
  14. {
  15. #endif // _LIBCPP_NO_EXCEPTIONS
  16. flag = 1;
  17. pthread_mutex_unlock(&mut);
  18. func(arg);
  19. pthread_mutex_lock(&mut);
  20. flag = ~0ul;
  21. pthread_mutex_unlock(&mut);
  22. pthread_cond_broadcast(&cv);
  23. #ifndef _LIBCPP_NO_EXCEPTIONS
  24. }
  25. catch (...)
  26. {
  27. pthread_mutex_lock(&mut);
  28. flag = 0ul;
  29. pthread_mutex_unlock(&mut);
  30. pthread_cond_broadcast(&cv);
  31. throw;
  32. }
  33. #endif // _LIBCPP_NO_EXCEPTIONS
  34. }
  35. else
  36. pthread_mutex_unlock(&mut);
  37. }

里面用了全局的mutex和condition来做同步。还有异常处理的代码。

事实上当看到mutext和condition时。就明确是怎样实现的了。

里面有一系列的同步操作。能够參考另外一篇blog:

http://blog.csdn.net/hengyunabc/article/details/27969613   并行编程之条件变量(posix condition variables)

虽然代码看起来非常easy,可是要细致分析它的各种时序也比較复杂。

有个地方比較疑惑的:

对于同步的__state__变量,并没有不论什么的memory order的保护,会不会有问题?

由于在JDK的代码里LockSupport和逻辑和上面的__call_once函数类似,可是却有memory order相关的代码:

OrderAccess::fence();

其他的东东:

有个东东值得提一下,在C++中。static变量的初始化,并非线程安全的。

比方

  1. void func(){
  2. static int value = 100;
  3. ...
  4. }

实际上相当于这种代码:

i

  1. nt __flag = 0
  2. void func(){
  3. static int value;
  4. if(!__flag){
  5. value = 100;
  6. __flag = 1;
  7. }
  8. ...
  9. }

总结:

另一件事情要考虑:全部的once_flag和call_once都共用全局的mutex和condition会不会有性能问题?

首先,像call_once这种需求在一个程序里不会太多。另外,临界区的代码是比較非常少的,仅仅有推断各自的flag的代码。

假设有上百上千个线程在等待once_flag,那么pthread_cond_broadcast可能会造成“惊群”效果,可是假设有那么多的线程都上等待。显然程序设计有问题。

另一个要注意的地方是once_flag的生命周期。它必需要比使用它的线程的生命周期要长。所以通常定义成全局变量比較好。

參考:

http://libcxx.llvm.org/

http://en.cppreference.com/w/cpp/thread/once_flag

http://en.cppreference.com/w/cpp/thread/call_once

版权声明:本文博客原创文章,博客,未经同意,不得转载。

C++11于once_flag,call_once分析的实现的更多相关文章

  1. [转载] 关于“淘宝应对"双11"的技术架构分析”

    微博上一篇最新的关于“淘宝应对"双11"的技术架构分析”.数据产品的一个最大特点是数据的非实时写入.

  2. Linux0.11内核--fork进程分析

    [版权所有,转载请注明出处.出处:http://www.cnblogs.com/joey-hua/p/5597818.html ] 据说安卓应用里通过fork子进程的方式可以防止应用被杀,大概原理就是 ...

  3. linux0.11下的中断机制分析

    http://orbt.blog.163.com/     异常就是控制流中的突变,用来响应处理器状态中的某些变化.当处理器检测到有事件发生时,它就会通过一张叫做异常表的跳转表,进行一个间接过程调用, ...

  4. odoo 11 之signup_with_phone模块分析

    signup_with_phone模块的主要功能是允许用户用自己的手机号作为注册登录账号,这里会进行手机号码格式的严格检查,该模块依赖odoo自带的auth_signup注册模块. 该项目地址在htt ...

  5. Linux0.11内核--系统调用机制分析

    [版权所有,转载请注明出处.出处:http://www.cnblogs.com/joey-hua/p/5570691.html ] Linux内核从启动到初始化也看了好些个源码文件了,这次看到kern ...

  6. 淘宝应对"双11"的技术架构分析

    原文地址:http://kb.cnblogs.com/page/193670/ 双“11”最热门的话题是TB ,最近正好和阿里的一个朋友聊淘宝的技术架构,发现很多有意思的地方,分享一下他们的解析资料: ...

  7. rtems 4.11 部分m4文件分析

    本来想把configure.ac和各种m4文件分析明白,发现有点困难,不过好在也能理解一些. 基本教程 首先要明白m4,参见这个教程,写得不错,不论怎么样m4替换来替换去的,还真是不那么容易懂,好在我 ...

  8. 低功耗蓝牙4.0BLE编程-nrf51822开发(11)-蓝牙串口代码分析

    代码实例:点击打开链接 实现的功能是从uart口发送数据至另一个蓝牙串口,或是从蓝牙读取数据通过uart打印出数据. int main(void) { // Initialize leds_init( ...

  9. 11.C++-临时对象分析

    首先来参考以下代码: #include <stdio.h> class Test { int mi; public: Test(int i) { mi = i; } Test() { Te ...

随机推荐

  1. friend keyword 对于模板 并不只不过友元!!!

    friend是C++中封装的漏网之鱼. C++中的friend同意其它的类或者是函数訪问本类的不论什么成员.甚至是private成员,仅仅要该类声明其为友元. 但是,在有些情况下,并非同意外界訪问类的 ...

  2. json2.js参考

    json2.js使用參考 json2.js提供了json的序列化和反序列化方法,能够将一个json对象转换成json字符串,也能够将一个json字符串转换成一个json对象. <html> ...

  3. 希尔排序java

    希尔排序简述 希尔排序是基于插入排序的以下两点性质而提出改进方法的: 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率.(希尔排序先将部分数据进行排序,相当于已经部分排好序) ...

  4. ORACLE触发特定的解释

    ORACLE PL/SQL编程八: 把触发器说透 本篇主要内容例如以下: 8.1 触发器类型 8.1.1 DML触发器 8.1.2 替代触发器 8.1.3 系统触发器 8.2 创建触发器 8.2.1 ...

  5. python udp编程实例

    与python tcp编程控制见 http://blog.csdn.net/aspnet_lyc/article/details/39854569 c++ udp/tcp 编程见 http://blo ...

  6. WIZnet通过启动在线培训活动:计算机网络TCP/IP协议而事实上,现在的方法

    为了给大家营造更好的学习环境.WIZnet特此举办第一期培训活动,由WIZnet一线project师为你分享最最前沿和有用的网络技术知识,帮你解答开发过程中的疑问.欢迎前来交流.名额有限(20名满), ...

  7. Search Bars(一个)

    A search bar provides an interface for text-based searches with a text box and buttons such as searc ...

  8. .NET 并行(多核)编程系列之六 Task基础部分完结篇

    原文:.NET 并行(多核)编程系列之六 Task基础部分完结篇 .NET 并行(多核)编程系列之六 Task基础部分完结篇 前言:之前的文章介绍了了并行编程的一些基本的,也注重的讲述了Task的一些 ...

  9. 讲座:采用Store检查邮件(1)

    讲座:采用Store检查邮件(1) 一.邮件接收的体系结构 JavaMail API中定义了一个java.mail.Store类,用于运行邮件的接收任务,该类的实例对象封装了某种邮件接收协议的底层实施 ...

  10. MyReport报表引擎2.0.0.0新功能

    Web报表引擎:Web上的良好的打印解决方式,WinForm的打印预览体现,报表自己主动化,支持直接打印,页小计,统计,转成金额大写,一维码显示等功能,满足中国式报表的常见功能需求.Web报表编辑器: ...