C++11并发支持库函数std::call_once

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::call_once,所以需要被引入进来。

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很失败,而且会失败N次(不容易啊)。它是一个无参的functor,被传递给call_once不停地重试,直到被成功调用了一次。这个场景小伙伴们在现实中遇到过吗?有兴趣的同学可以写一个通用的类Retry来封装一些需要重试的操作(例如连接数据库,处理HTTP 429等等)。

今天时间不够了,先打住吧。这里只是hello world,没有包含callee的参数传递和绑定,也没有demo在多线程环境中的真实cases。明天我尽力吧(也许后天,也许大后天,也许。。。)

p.s. 更新

更多的例子看这里

在C++多线程中使用std::call_once保证仅仅执行一次_Bruce Jia(Autodesk)的博客-CSDN博客

给C++ std::call_once传递可变参数_Bruce Jia(Autodesk)的博客-CSDN博客

你可能感兴趣的:(C++,c++,开发语言)