C++语言自从C++11开始对并发编程(concurrency)做了很多的支持,例如atomic, thread, mutex, condition_variable, lock, async, future 等等众多喜闻乐见的好宝贝,另外不那么招人注意的也有几个值得称赞一番,例如std::call_once。
这个函数从字面上理解就是保证被调用者(普通函数,类成员函数,functor或lambda等等任何满足callable概念的)只被调用一次,不管是单线程还是多线程的情况下。
#include
#include
int counter = 0;
void increaseCounter()
{
std::cout << "counter is increased to " << ++counter << std::endl;
}
void decreaseCounter()
{
std::cout << "counter is decreased to " << --counter << std::endl;
}
void showCounter()
{
std::cout << "couter is " << counter << std::endl;
}
上面示例代码中定义了一个“散装的”全局计数器counter,和它的几个相关操作(仅仅demo只用,同学们实际工作中还是要讲究一些)。注意头文件
std::once_flag flag;
void demo1()
{
// in same thread, when flag is shared, the function is called only once.
std::call_once(flag, increaseCounter);
std::call_once(flag, increaseCounter);
std::call_once(flag, increaseCounter);
}
最简单的情况下,单一线程中使用同一个flag尝试调用increaseCounter多次,实际上只有第一次会成功。那么如果使用同一个flag调用不同函数呢?
void demo_one_flag_for_multi_callees()
{
// flag is shared for multi calls of call_once
// but only one returning call can be invoked.
std::call_once(flag, decreaseCounter);
std::call_once(flag, increaseCounter);
std::call_once(flag, showCounter);
}
上面代码中三次调用call_once,但都是用的一个flag,只有第一个调用会真正执行。
第一个参数flag, 类型是std::once_flag, 作为一个标识来表示是否已经有callee被成功调用了。注意文档上写得其实不准确 std::call_once - cppreference.com,小伙伴们可以自己去对照体会。
可以将flag理解为一个一次性的开关,配合std::call_once使用,而且只能使用一次。
std::call_once一个好用之处是对于异常的处理: 如果callee抛出了异常,则不算是一个成功的调用。所以对应的flag还可以使用。
std::once_flag flag_for_exc;
void demo_exceptions_simple()
{
std::call_once(flag_for_exc, [](){ throw "failed to say hello"; });
}
这里被调用的lambda抛出异常,异常会被传递到调用者,直到被处理或者把C++给整挂了(std::terminate)。
下面是一个复杂点的demo。
template
struct FailForTimes
{
int n;
FailForTimes() : n(N) {}
void operator()()
{
if (n > 0)
{
throw n--;
}
}
int get() const
{
return n;
}
};
void demo_exceptions_complex()
{
FailForTimes<3> ff3t;
while(true)
{
try
{
std::call_once(flag_for_exc, ff3t);
std::cout << "Result: " << ff3t.get() << std::endl;
// When we reach here, we've at least invoked the callee once.
// This is a good solutioon for "hard" initialization ...
break;
}
catch(int n)
{
std::cerr << "Caught exception of int - " << n << std::endl;
}
}
}
类FailForTime
今天时间不够了,先打住吧。这里只是hello world,没有包含callee的参数传递和绑定,也没有demo在多线程环境中的真实cases。明天我尽力吧(也许后天,也许大后天,也许。。。)
p.s. 更新
更多的例子看这里
在C++多线程中使用std::call_once保证仅仅执行一次_Bruce Jia(Autodesk)的博客-CSDN博客
给C++ std::call_once传递可变参数_Bruce Jia(Autodesk)的博客-CSDN博客