我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++中多线程std::call_once怎么用

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C++中多线程std::call_once怎么用

这篇文章主要介绍了C++中多线程std::call_once怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

在多线程的环境下,有些时候我们不需要某个函数被调用多次或者某些变量被初始化多次,它们仅仅只需要被调用一次或者初始化一次即可。很多时候我们为了初始化某些数据会写出如下代码,这些代码在单线程中是没有任何问题的,但是在多线程中就会出现不可预知的问题。

bool initialized = false;void foo() {    if (!initialized) {        do_initialize ();  //1        initialized = true;    }}

为了解决上述多线程中出现的资源竞争导致的数据不一致问题,我们大多数的处理方法就是使用互斥锁来处理。只要上面①处进行保护,这样共享数据对于并发访问就是安全的。如下:

bool initialized = false;std::mutex resource_mutex;void foo() {    std::unique_lock<std::mutex> lk(resource_mutex);  // 所有线程在此序列化     if(!initialized) {        do_initialize ();  // 只有初始化过程需要保护     }    initialized = true;    lk.unlock();    // do other;}

但是,为了确保数据源已经初始化,每个线程都必须等待互斥量。为此,还有人想到使用“双重检查锁模式”的办法来提高效率,如下:

bool initialized = false;std::mutex resource_mutex;void foo() {    if(!initialized) {  // 1        std::unique_lock<std::mutex> lk(resource_mutex);  // 2 所有线程在此序列化         if(!initialized) {            do_initialize ();  // 3 只有初始化过程需要保护         }        initialized = true;    }    // do other;  // 4}

第一次读取变量initialized时不需要获取锁①,并且只有在initialized为false时才需要获取锁。然后,当获取锁之后,会再检查一次initialized变量② (这就是双重检查的部分),避免另一线程在第一次检查后再做初始化,并且让当前线程获取锁。

但是上面这种情况也存在一定的风险,具体可以查阅著名的《C++和双重检查锁定模式(DCLP)的风险》。

对此,C++标准委员会也认为条件竞争的处理很重要,所以C++标准库提供了更好的处理方法:使用std::call_once函数来处理,其定义在头文件#include<mutex>中。std::call_once函数配合std::once_flag可以实现:多个线程同时调用某个函数,它可以保证多个线程对该函数只调用一次。它的定义如下:

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,它只用默认构造函数构造,不能拷贝不能移动,表示函数的一种内在状态。后面两个参数很好理解,第一个传入的是一个Callable。Callable简单来说就是可调用的东西,大家熟悉的有函数、函数对象(重载了operator()的类)、std::function和函数指针,C++11新标准中还有std::bindlambda(可以查看我的上一篇文章)。最后一个参数就是你要传入的参数。 在使用的时候我们只需要定义一个non-local的std::once_flag(非函数局部作用域内的),在调用时传入参数即可,如下所示:

#include <iostream>#include <thread>#include <mutex> std::once_flag flag1;void simple_do_once() {    std::call_once(flag1, [](){ std::cout << "Simple example: called once\n"; });} int main() {    std::thread st1(simple_do_once);    std::thread st2(simple_do_once);    std::thread st3(simple_do_once);    std::thread st4(simple_do_once);    st1.join();    st2.join();    st3.join();    st4.join();}

call_once保证函数func只被执行一次,如果有多个线程同时执行函数func调用,则只有一个活动线程(active call)会执行函数,其他的线程在这个线程执行返回之前会处于”passive execution”(被动执行状态)&mdash;&mdash;不会直接返回,直到活动线程对func调用结束才返回。对于所有调用函数func的并发线程,数据可见性都是同步的(一致的)。

但是,如果活动线程在执行func时抛出异常,则会从处于”passive execution”状态的线程中挑一个线程成为活动线程继续执行func,依此类推。一旦活动线程返回,所有”passive execution”状态的线程也返回,不会成为活动线程。(实际上once_flag相当于一个锁,使用它的线程都会在上面等待,只有一个线程允许执行。如果该线程抛出异常,那么从等待中的线程中选择一个,重复上面的流程)。

std::call_once在签名设计时也很好地考虑到了参数传递的开销问题,可以看到,不管是Callable还是Args,都使用了&&作为形参。他使用了一个template中的reference fold(我前面的文章也有介绍过),简单分析:

  • 如果传入的是一个右值,那么Args将会被推断为Args

  • 如果传入的是一个const左值,那么Args将会被推断为const Args&

  • 如果传入的是一个non-const的左值,那么Args将会被推断为Args&

也就是说,不管你传入的参数是什么,最终到达std::call_once内部时,都会是参数的引用(右值引用或者左值引用),所以说是零拷贝的。那么还有一步呢,我们还得把参数传到可调用对象里面执行我们要执行的函数,这一步同样做到了零拷贝,这里用到了另一个标准库的技术std::forward(我前面的文章也有介绍过)。

如下,如果在函数执行中抛出了异常,那么会有另一个在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();}

std::call_once 也可以用在类中:

#include <iostream>#include <mutex>#include <thread>class A { public:  void f() {    std::call_once(flag_, &A::print, this);    std::cout << 2;  } private:  void print() { std::cout << 1; } private:  std::once_flag flag_;};int main() {  A a;  std::thread t1{&A::f, &a};  std::thread t2{&A::f, &a};  t1.join();  t2.join();}  // 122

还有一种初始化过程中潜存着条件竞争:static 局部变量在声明后就完成了初始化,这存在潜在的 race condition,如果多线程的控制流同时到达 static 局部变量的声明处,即使变量已在一个线程中初始化,其他线程并不知晓,仍会对其尝试初始化。很多在不支持C++11标准的编译器上,在实践过程中,这样的条件竞争是确实存在的,为此,C++11 规定,如果 static 局部变量正在初始化,线程到达此处时,将等待其完成,从而避免了 race condition,只有一个全局实例时,对于C++11,可以直接用 static 而不需要 std::call_once,也就是说,在只需要一个全局实例情况下,可以成为std::call_once的替代方案,典型的就是单例模式了:

template <typename T>class Singleton { public:  static T& Instance();  Singleton(const Singleton&) = delete;  Singleton& operator=(const Singleton&) = delete; private:  Singleton() = default;  ~Singleton() = default;};template <typename T>T& Singleton<T>::Instance() {  static T instance;  return instance;}

感谢你能够认真阅读完这篇文章,希望小编分享的“C++中多线程std::call_once怎么用”这篇文章对大家有帮助,同时也希望大家多多支持编程网,关注编程网行业资讯频道,更多相关知识等着你来学习!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++中多线程std::call_once怎么用

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C++中多线程std::call_once怎么用

这篇文章主要介绍了C++中多线程std::call_once怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。在多线程的环境下,有些时候我们不需要某个函数被调用多次或者某些
2023-06-29

C++中std::thread线程怎么使用

本篇内容主要讲解“C++中std::thread线程怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++中std::thread线程怎么使用”吧!1:std::thread的基本用法最简
2023-07-04

C++中std::thread线程用法

本文主要介绍了C++中std::thread线程用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-01-08

c++中std::怎么用

std 是 c++ 中包含标准库组件的命名空间。为了使用 std,需要使用 "using namespace std;" 语句。直接使用 std 命名空间中的符号可以简化代码,但建议仅在需要时使用,以避免命名空间污染。std 在 C++ 中
c++中std::怎么用
2024-05-09

C#多线程中lock怎么用

这篇文章主要介绍了C#多线程中lock怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。下面就是一段简单的代码。public class AccessControl()
2023-06-17

C#多线程怎么用

这篇文章主要为大家展示了“C#多线程怎么用”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“C#多线程怎么用”这篇文章吧。一、基本概念1、进程首先打开任务管理器,查看当前运行的进程:从任务管理器里面
2023-06-22

C++语言中std::array怎么用

这篇文章给大家分享的是有关C++语言中std::array怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。概述std::array是在C++11标准中增加的STL容器,它的设计目的是提供与原生数组类似的功能与
2023-06-15

C++20中的std::span怎么使用

这篇文章主要讲解了“C++20中的std::span怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++20中的std::span怎么使用”吧!span就是一个连续对象存储的观察者。
2023-07-05

c语言多线程怎么用

c 语言中多线程的使用是指一种计算机技术,允许一个程序同时执行多个任务。具体实现步骤包括:1. 创建线程;2. 定义线程函数;3. 加入线程;4. 取消线程。为了确保线程安全地访问共享数据,可以使用线程同步机制,例如互斥量、条件变量和信号量
c语言多线程怎么用
2024-05-15

C++中STL标准库std::vector怎么用

小编给大家分享一下C++中STL标准库std::vector怎么用,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1. 简介vector 是表示可以改变大小的数组的
2023-06-29

C#多线程中的互斥锁Mutex怎么用

本篇内容主要讲解“C#多线程中的互斥锁Mutex怎么用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C#多线程中的互斥锁Mutex怎么用”吧!一、简介Mutex的突出特点是可以跨应用程序域边界对
2023-06-30

c语言怎么使用多线程

什么是多线程?多线程是一种并发编程技术,允许程序同时执行多个任务或线程。c 语言使用 posix 线程库创建和管理线程,步骤如下:创建线程加入线程线程函数同步(使用互斥锁、条件变量和信号量)注意:共享数据时使用同步机制。错误处理对于多线程编
c语言怎么使用多线程
2024-05-21

C#中单例模式与多线程怎么用

这篇文章给大家分享的是有关C#中单例模式与多线程怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、单例模式我们先来看看两种创建单例模式的示例代码。1、饿汉式 饿汉式创建单例模式是在程序里面直接初始化了一个对
2023-06-29

C#多线程之Parallel类怎么用

本文小编为大家详细介绍“C#多线程之Parallel类怎么用”,内容详细,步骤清晰,细节处理妥当,希望这篇“C#多线程之Parallel类怎么用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。Parallel类是对
2023-06-30

c语言怎么调用多线程

在C语言中,可以使用线程库来调用多线程。C语言标准库并不直接提供多线程支持,但是你可以使用第三方库如POSIX threads(pthread)库或Windows线程库等来实现多线程编程。下面是一个使用POSIX threads库进行多线程
2023-09-15

C#中怎么实现多线程安全

C#中怎么实现多线程安全,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。C#多线程控制进度条之多线程安全的问题:我们知道 Windows 编程中有一个必须遵守的原则,那就是在一个
2023-06-17

C++ 多线程编程中线程池的应用

c++++ 多线程编程中使用线程池的好处包括:1)减少线程创建次数;2)负载均衡;3)避免资源争用。例如,通过使用线程池将图像转换任务分配给线程池,可以提高文件转换应用程序的转换速度。C++ 多线程编程中线程池的应用在现代 C++ 应用程
C++ 多线程编程中线程池的应用
2024-05-14

怎么在c#中利用多线程处理多个数据

本篇文章为大家展示了怎么在c#中利用多线程处理多个数据,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。概述多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多
2023-06-14

C#多线程锁lock和Monitor怎么用

本文小编为大家详细介绍“C#多线程锁lock和Monitor怎么用”,内容详细,步骤清晰,细节处理妥当,希望这篇“C#多线程锁lock和Monitor怎么用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。1,Loc
2023-06-29

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录