首页 > 编程语言 >C++ call_once详解

C++ call_once详解

时间:2024-08-11 10:53:09浏览次数:12  
标签:std C++ flag 线程 call include once

引言

在多线程编程中,常常需要确保某些初始化操作只执行一次,例如初始化一个全局资源或单例模式中的实例创建。C++11引入了std::call_oncestd::once_flag,为这种需求提供了便捷和高效的解决方案。

一、基本概念

1. std::call_once

std::call_once是一个函数模板,它确保某个函数在多个线程中只被调用一次。其原型如下:

template< class Callable, class... Args > void call_once( std::once_flag& flag, Callable&& func, Args&&... args );

  • flag:一个std::once_flag类型的标志,用来记录是否已经调用过。
  • func:要执行的函数,可以是函数指针、函数对象或lambda表达式。
  • args:传递给func的参数。

2. std::once_flag

std::once_flag是一个轻量级的同步原语,用于标识某个操作是否已经执行过。默认构造的std::once_flag处于未调用状态。

二、std::call_once的使用示例

以下是一个简单示例,演示如何使用std::call_oncestd::once_flag

#include <iostream>
#include <thread>
#include <mutex>

std::once_flag flag;

void initialize() {
    std::cout << "Resource initialized." << std::endl;
}

void thread_func() {
    std::call_once(flag, initialize);
    std::cout << "Thread executed." << std::endl;
}

int main() {
    std::thread t1(thread_func);
    std::thread t2(thread_func);
    std::thread t3(thread_func);

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

在上述代码中,无论多少个线程调用thread_funcinitialize函数只会执行一次,输出结果如下:

Resource initialized.
Thread executed.
Thread executed.
Thread executed.

//调用其他类的函数

std::call_once(flag,&VideoWidget::threadStart,ui->videoWidget);

三、std::call_once的实际应用

1. 单例模式

单例模式确保一个类只有一个实例,并提供一个全局访问点。使用std::call_once可以简化线程安全的单例实现:

#include <iostream>
#include <mutex>

class Singleton {
public:
    static Singleton& getInstance() {
        std::call_once(initFlag, &Singleton::initSingleton);
        return *instance;
    }

    void doSomething() {
        std::cout << "Singleton instance doing something." << std::endl;
    }

private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static void initSingleton() {
        instance = new Singleton();
    }

    static Singleton* instance;
    static std::once_flag initFlag;
};

Singleton* Singleton::instance = nullptr;
std::once_flag Singleton::initFlag;

int main() {
    Singleton& s1 = Singleton::getInstance();
    Singleton& s2 = Singleton::getInstance();

    s1.doSomething();
    s2.doSomething();

    return 0;
}

在这个示例中,无论多少个线程调用getInstanceSingleton实例只会被创建一次。

2. 全局资源初始化

在多线程程序中,全局资源的初始化需要确保线程安全,例如初始化一个配置文件或数据库连接:

#include <iostream>
#include <thread>
#include <mutex>
#include <fstream>

std::once_flag configFlag;

void loadConfig() {
    std::ifstream configFile("config.txt");
    if (configFile) {
        std::cout << "Configuration loaded." << std::endl;
    } else {
        std::cout << "Failed to load configuration." << std::endl;
    }
}

void thread_func() {
    std::call_once(configFlag, loadConfig);
    std::cout << "Thread executed." << std::endl;
}

int main() {
    std::thread t1(thread_func);
    std::thread t2(thread_func);
    std::thread t3(thread_func);

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

在上述代码中,loadConfig函数只会执行一次,从而保证配置文件只被加载一次。

四、std::call_once的实现原理

std::call_once通常依赖于底层操作系统提供的同步原语,例如POSIX线程中的pthread_once。它通过std::once_flag来记录操作是否已经执行,并使用适当的同步机制(例如互斥锁或原子操作)来保证线程安全。

当多个线程同时调用std::call_once时,只有一个线程能够成功进入并执行指定的函数,其它线程则会被阻塞,直到函数执行完毕。一旦函数执行完毕,所有等待的线程会继续执行后续代码。

五、注意事项

  1. std::call_once保证线程安全,但可能会带来一定的性能开销,特别是在多次调用的情况下。
  2. std::once_flag不能被复制或移动,因此必须小心管理其生命周期,确保其在所有线程中可访问。
  3. std::call_once调用的函数应该是无异常的,因为异常可能会导致std::once_flag状态的不一致。

六、总结

std::call_oncestd::once_flag为C++多线程编程提供了简洁而高效的单次调用机制。无论是实现单例模式还是全局资源的初始化,它们都能确保操作的线程安全性。通过合理使用std::call_once,可以避免复杂的同步代码,使得多线程编程更加可靠和易于维护。

标签:std,C++,flag,线程,call,include,once
From: https://blog.csdn.net/m0_74091159/article/details/140979027

相关文章

  • Skeleton Recall Loss 分割领域的新突破:极大的减少了资源消耗,还能提高性能
    精确分割在当今众多领域都是一项关键需求比如说自动驾驶汽车的训练、医学图像识别系统,以及通过卫星图像进行监测。在许多其他领域,当感兴趣的对象微小但至关重要时,例如研究血管流动、手术规划、检测建筑结构中的裂缝或优化路线规划,需要更高的精度。此前已经做了大量工作来解决这种......
  • C++11新特性
    C++11新特性语言特性移动语义右值引用转发引用可变模板列表初始化静态断言类型推导lambda表达式decltype类型声明nullptr空指针强类型枚举属性constexpr常量表达式委托构造用户定义语义显式虚重载Final限定符:限定的东西无法......
  • C++20新特性
    C++20新特性语言特性协程concept概念指定初始化器lambda表达式模板语义范围for循环增加初始化器[[likely]][unlikely]属性废弃隐式捕获this非类型模板参数的类类型constexpr虚函数explict(bool)立即函数usingenums,能直接进行using了lambda捕获参数包char8_t类型......
  • visual studio code安装与C/C++语言运行
    VisualStudioCode(VSCode)安装与C/C++语言运行需要几个步骤,包括安装VSCode、配置C/C++环境以及安装必要的插件。以下是一个详细的步骤指南:1.安装VSCode访问官网:首先,前往VSCode的官方网站 VisualStudioCode-CodeEditing.Redefined 进行下载。下载并安装:选择......
  • c++的类和对象(中):默认成员函数与运算符重载(重难点!!)
    前言 Hello,小伙伴们,我们今天继续c++的学习,我们上期有介绍到c++的部分特性,以及一些区别于c语言的地方,今天我们将继续深入了解c++的类和对象,探索c++的奥秘。好,废话不多说,开始我们今天的学习。 1.类默认成员函数默认成员函数就是用户没有显示实现,编译器会自动生成的函数称......
  • 杨辉三角 C++实现
    给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。在「杨辉三角」中,每个数是它左上方和右上方的数的和。classSolution{public:vector<vector<int>>generate(intnumRows){vector<vector<int>>vv;vv.resize(numRows);......
  • C++17新特性
    C++17新特性语言特性使用auto声明非类型模板参量折叠表达式提供模板参数包的折叠template<typename...Args>boollogicalAnd(Args...args){//二元折叠return(true&&...&&args);}boolb=false;bool&b2=b;logicalAnd(b,b2,true);//==fa......
  • 【C++】马蹄集05 最大默契
    小码哥和小码妹是好朋友,他们有时会用一种方式检测双方的默契程度:两人分别给出一个字符串8和并进行若干次操作使s串变得和t串一样。操作分为两种:1.删除s串的第一个字符;2.将一个新字符插在s串的第一个字符之前。如果可以用正好n次操作使s串变为t串,就意味着他们两人很有......
  • C++特性
    C++特性C++主要版本:可以通过draft/papersatmain·cplusplus/draft(github.com)查看C++草案。C++98:C++的第一个国际标准ISO/IEC14882:1998,包括的特性有:模板、标准模板库、命名空间、异常处理等。C++03:2003年发布的版本ISO/IEC14882:2003,包括的特性有:内联函数、操作......
  • C/C++数字与字符串互相转换
    前言:在C/C++程序中,会需要把数字与字符串做出互相转换的操作,用于实现程序想要的效果。下面将介绍多种方法实现数字与字符串互相转换。字符串转为数字一、利用ASCII我们知道每个字符都有一个ASCII码,利用这一点可以将字符-'0'转为数字。在字母大小写转换时也可以利用这个性质......