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

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

声明

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

struct once_flag
{
constexpr once_flag() noexcept;
once_flag(const once_flag&) = delete;
once_flag& operator=(const once_flag&) = delete;
};
template<class Callable, class ...Args>
void call_once(once_flag& flag, Callable&& func, Args&&... args); } // std

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

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

演示样例

看一个演示样例:

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

#include <iostream>
#include <thread>
#include <mutex> std::once_flag flag; void do_once()
{
std::call_once(flag, [](){ std::cout << "Called once" << std::endl; });
} int main()
{
std::thread t1(do_once);
std::thread t2(do_once);
std::thread t3(do_once);
std::thread t4(do_once); t1.join();
t2.join();
t3.join();
t4.join();
}

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

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

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

./a.out

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

Called once

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

比方以下的样例:

#include <iostream>
#include <thread>
#include <mutex> std::once_flag flag; inline void may_throw_function(bool do_throw)
{
// only one instance of this function can be run simultaneously
if (do_throw) {
std::cout << "throw\n"; // this message may be printed from 0 to 3 times
// if function exits via exception, another function selected
throw std::exception();
} std::cout << "once\n"; // printed exactly once, it's guaranteed that
// there are no messages after it
} inline void do_once(bool do_throw)
{
try {
std::call_once(flag, may_throw_function, do_throw);
}
catch (...) {
}
} int main()
{
std::thread t1(do_once, true);
std::thread t2(do_once, true);
std::thread t3(do_once, false);
std::thread t4(do_once, true); t1.join();
t2.join();
t3.join();
t4.join();
}

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

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

实现分析

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

struct once_flag
{
once_flag() _NOEXCEPT : __state_(0) {}
private:
once_flag(const once_flag&); // = delete;
once_flag& operator=(const once_flag&); // = delete; unsigned long __state_; template<class _Callable>
friend void call_once(once_flag&, _Callable);
};

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

template <class _Fp>
class __call_once_param
{
_Fp __f_;
public:
explicit __call_once_param(const _Fp& __f) : __f_(__f) {}
void operator()()
{
__f_();
}
};
template<class _Callable>
void call_once(once_flag& __flag, _Callable __func)
{
if (__flag.__state_ != ~0ul)
{
__call_once_param<_Callable> __p(__func);
__call_once(__flag.__state_, &__p, &__call_once_proxy<_Callable>);
}
}

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

static pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cv = PTHREAD_COND_INITIALIZER; void
__call_once(volatile unsigned long& flag, void* arg, void(*func)(void*))
{
pthread_mutex_lock(&mut);
while (flag == 1)
pthread_cond_wait(&cv, &mut);
if (flag == 0)
{
#ifndef _LIBCPP_NO_EXCEPTIONS
try
{
#endif // _LIBCPP_NO_EXCEPTIONS
flag = 1;
pthread_mutex_unlock(&mut);
func(arg);
pthread_mutex_lock(&mut);
flag = ~0ul;
pthread_mutex_unlock(&mut);
pthread_cond_broadcast(&cv);
#ifndef _LIBCPP_NO_EXCEPTIONS
}
catch (...)
{
pthread_mutex_lock(&mut);
flag = 0ul;
pthread_mutex_unlock(&mut);
pthread_cond_broadcast(&cv);
throw;
}
#endif // _LIBCPP_NO_EXCEPTIONS
}
else
pthread_mutex_unlock(&mut);
}

里面用了全局的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变量的初始化,并非线程安全的。

比方

void func(){
static int value = 100;
...
}

实际上相当于这种代码:

i

nt __flag = 0
void func(){
static int value;
if(!__flag){
value = 100;
__flag = 1;
}
...
}

总结:

另一件事情要考虑:全部的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. J2SE基础:1.类和对象基础

    什么是对象 在Java语言,全部的人,事物或者模块都是一个对象. 同样的对象具有一些同样的特性. 狗,猫,蛇3个对象(动物的对象) 苹果,梨,桔子3个对象(水果的对象) 什么是类 能够将现实生活中的对 ...

  2. google多语言通信框架gRPC

    google多语言通信框架gRPC系列(一)概述 gRPC概述 3/26/2016 9:16:08 AM 目录 一.概述 二.编译gRPC 三.C#中使用gRPC 四.C++中使用gRPC 一直在寻找 ...

  3. C# 32位程序访问64位系统注册表

    原文:C# 32位程序访问64位系统注册表 我的上一篇文章已经阐述了“32位程序和64位程序在64位平台上读\写注册表的区别”,那么接下来将要回答上篇所留下来的一个问题:32位程序如何访问64位系统注 ...

  4. 怎样配置nginx同一时候执行不同版本号的php-fpm

    在/usr/local/php/etc/php-fpm.conf里找到 listen = 127.0.0.1:9000 将port9000改动为9001 在对应的nginx配置里也做相同的port改动

  5. ios发电子邮件

    ios发电子邮件 by 吴雪莹 第一: NSString *myEmail = @"3423423423@qq.com"; NSString *toemail = @"a ...

  6. 做web项目时对代码修改后浏览器端不生效的应对方法(持续更新)

    做web项目时,经常会遇到修改了代码,但浏览器端没有生效,原因是多种多样的,我会根据我遇到的情况逐步更新解决办法 1.运行的时候采用debug模式,一般情况下使用项目部署按钮右边那个按钮下的tomca ...

  7. 排列组合相关算法 python

    获取指定长度得全部序列 通过事件来表述这个序列,即n重伯努利实验(二项分布)的全部可能结果.比如时间a表示为: a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 假设每次实验为从 ...

  8. (转)FFMPEG解码流程

    http://www.douban.com/note/228831821/ FFMPEG解码流程:     1. 注册所有容器格式和CODEC: av_register_all()     2. 打开 ...

  9. 十大经典数据挖掘算法(9) 朴素贝叶斯分类器 Naive Bayes

    贝叶斯分类器 贝叶斯分类分类原则是一个对象的通过先验概率.贝叶斯后验概率公式后计算,也就是说,该对象属于一类的概率.选择具有最大后验概率的类作为对象的类属.现在更多的研究贝叶斯分类器,有四个,每间:N ...

  10. 《反project核心原则》说明

      致亲爱的中国读者: 大家好 !我是<逆向project核心原理> 作者 李承远(ReverseCore). (韩文博客地址:www.reversecore.com) 首先.非常高兴我的 ...