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


    C++语言自从C++11开始对并发编程(concurrency)做了很多的支持,例如atomic, thread, mutex,  condition_variable, lock, async, future 等等众多喜闻乐见的好宝贝,另外不那么招人注意的也有几个值得称赞一番,例如std::call_once。

    这个函数从字面上理解就是保证被调用者(普通函数,类成员函数,functor或lambda等等任何满足callable概念的)只被调用一次,不管是单线程还是多线程的情况下。

    1. #include
    2. #include
    3. int counter = 0;
    4. void increaseCounter()
    5. {
    6. std::cout << "counter is increased to " << ++counter << std::endl;
    7. }
    8. void decreaseCounter()
    9. {
    10. std::cout << "counter is decreased to " << --counter << std::endl;
    11. }
    12. void showCounter()
    13. {
    14. std::cout << "couter is " << counter << std::endl;
    15. }

    上面示例代码中定义了一个“散装的”全局计数器counter,和它的几个相关操作(仅仅demo只用,同学们实际工作中还是要讲究一些)。注意头文件中定义了std::call_once,所以需要被引入进来。

    1. std::once_flag flag;
    2. void demo1()
    3. {
    4. // in same thread, when flag is shared, the function is called only once.
    5. std::call_once(flag, increaseCounter);
    6. std::call_once(flag, increaseCounter);
    7. std::call_once(flag, increaseCounter);
    8. }

    最简单的情况下,单一线程中使用同一个flag尝试调用increaseCounter多次,实际上只有第一次会成功。那么如果使用同一个flag调用不同函数呢?

    1. void demo_one_flag_for_multi_callees()
    2. {
    3. // flag is shared for multi calls of call_once
    4. // but only one returning call can be invoked.
    5. std::call_once(flag, decreaseCounter);
    6. std::call_once(flag, increaseCounter);
    7. std::call_once(flag, showCounter);
    8. }

    上面代码中三次调用call_once,但都是用的一个flag,只有第一个调用会真正执行。

    第一个参数flag, 类型是std::once_flag, 作为一个标识来表示是否已经有callee被成功调用了。注意文档上写得其实不准确 std::call_once - cppreference.com,小伙伴们可以自己去对照体会。

    可以将flag理解为一个一次性的开关,配合std::call_once使用,而且只能使用一次。

    std::call_once一个好用之处是对于异常的处理: 如果callee抛出了异常,则不算是一个成功的调用。所以对应的flag还可以使用。

    1. std::once_flag flag_for_exc;
    2. void demo_exceptions_simple()
    3. {
    4. std::call_once(flag_for_exc, [](){ throw "failed to say hello"; });
    5. }

    这里被调用的lambda抛出异常,异常会被传递到调用者,直到被处理或者把C++给整挂了(std::terminate)。

    下面是一个复杂点的demo。

    1. template<int N>
    2. struct FailForTimes
    3. {
    4. int n;
    5. FailForTimes() : n(N) {}
    6. void operator()()
    7. {
    8. if (n > 0)
    9. {
    10. throw n--;
    11. }
    12. }
    13. int get() const
    14. {
    15. return n;
    16. }
    17. };
    18. void demo_exceptions_complex()
    19. {
    20. FailForTimes<3> ff3t;
    21. while(true)
    22. {
    23. try
    24. {
    25. std::call_once(flag_for_exc, ff3t);
    26. std::cout << "Result: " << ff3t.get() << std::endl;
    27. // When we reach here, we've at least invoked the callee once.
    28. // This is a good solutioon for "hard" initialization ...
    29. break;
    30. }
    31. catch(int n)
    32. {
    33. std::cerr << "Caught exception of int - " << n << std::endl;
    34. }
    35. }
    36. }

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

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

  • 相关阅读:
    安装和使用
    934. 最短的桥
    nginx源码分析--双端列表
    竞赛 基于机器视觉的车道线检测
    Encoder——Decoder工作原理与代码支撑
    注入常考面试题总结
    大厂真题:【位运算】米哈游2023秋招-相加异或
    HT513 I2S输入的音频功放的应用场景
    !还不了解位操作符?????!!!!!
    Chapter10 : Deep Neural Networks for QSAR
  • 原文地址:https://blog.csdn.net/IDisposable/article/details/125882888