C++ call_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,可以避免复杂的同步代码,使得多线程编程更加可靠和易于维护。

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值