C++多线程编程之std::call_once

目录

1.引言

2.std::once_flag

3.std::call_once

4.使用场景

5.总结


1.引言

        在多线程编程中,确保某些操作只执行一次是一个常见需求。std::call_once 是 C++11 中引入的一个模版函数,它能帮助我们在多线程环境下实现“单次调用”(once-only)的功能,避免重复执行同一操作。

        常用的场景如Init()操作或一些系统参数的获取等。相对来说,std::call_once用法比较简单,配合std::once_flag即可实现。

2.std::once_flag

  std::once_flag 是一个简单的标志类,用于指示某个初始化操作是否已经完成。它通常与 std::call_once 一起使用。std::once_flag 的实例在创建时处于未设置状态,表示相关的初始化操作尚未执行。一旦通过 std::call_once 成功执行了初始化操作,std::once_flag 的状态就被设置为已设置,表示初始化已经完成。

        它的特征有:

  • 一次性std::once_flag 的状态在创建时是未设置的(即表示初始化操作尚未执行)。一旦通过 std::call_once 成功执行了与 std::once_flag 关联的操作,std::once_flag 的状态就被设置为已设置,并且之后即使再次调用 std::call_once,也不会再次执行该操作。
  • 线程安全std::once_flag 的设计保证了它是线程安全的,可以在多个线程之间安全地共享和修改。
  • 不可复制std::once_flag 对象是不可复制的,这意味着你不能创建一个 std::once_flag 的副本。这是为了确保状态的一致性,防止由于复制操作而导致的状态混乱。
  • 不可移动:同样地,std::once_flag 对象也是不可移动的,即你不能使用移动语义来转移 std::once_flag 的所有权。

3.std::call_once

  std::call_once 的设计目的是为了保证某个操作(如初始化)在多线程程序中只被调用一次。它结合了std::mutex 的功能,确保在程序的不同线程中,只有第一个调用该操作的线程会执行操作,其他线程会被阻塞,直到初始化操作完成,然后继续执行,但不会再次调用可调用对象。

        在多线程程序中,通常有多种方法来同步线程的执行。std::call_once 使用了一个称为“标志”(std::once_flag)的机制来标记某个操作是否已经执行过。只有当标志没有被设置时,操作才会被执行。其他线程会等待该操作完成,确保只执行一次。

  std::call_once 接受两个参数:

  • once_flag:一个std::once_flag 类型的对象,用于标记该操作是否已经执行过。

  • Function:一个可调用对象(如函数、函数指针、lambda 表达式等),这是要执行的操作。

基本用法:

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

static std::once_flag flag;

void init() {
    std::cout << "Initialization function called once." << std::endl;
}

void worker(int id) {
    std::call_once(flag, init);
    std::cout << "Worker thread " << id << " is running." << std::endl;
}

int main() {
    std::thread t1(worker, 1);
    std::thread t2(worker, 2);
    std::thread t3(worker, 3);

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

    return 0;
}

输出:

Initialization function called once.
Worker thread 1 is running.
Worker thread 2 is running.
Worker thread 3 is running.

在这个例子中,尽管有三个线程调用std::call_once,但是init 只有在第一次调用时执行一次。其他线程等待操作完成后就会跳过该操作。

4.使用场景

1)单例模式的实现
在单例模式中,类的实例只被创建一次,并且这个实例通过一个静态指针或引用被全局访问。std::call_once 可以确保即使多个线程同时尝试创建实例,也只会有一个线程成功执行创建操作。

2)延迟初始化
有些资源或对象可能非常昂贵或耗时来创建,但并非在程序启动时就需要。使用 std::call_once,可以在第一次真正需要使用这些资源时才进行初始化,从而节省时间和资源。

3)静态局部变量的线程安全初始化
在 C++ 中,静态局部变量的初始化是线程不安全的(直到 C++11)。使用 std::call_once 可以确保静态局部变量的初始化在多线程环境中也是安全的。

4)配置或数据的加载
如果程序需要从文件、数据库或其他外部源加载配置或数据,并且这些数据在程序运行期间不会改变,那么可以使用 std::call_once 来确保这些数据只被加载一次。

5)线程安全的日志记录初始化
如果程序使用线程安全的日志记录库,并且这个库需要在第一次记录日志之前进行一些初始化工作,那么可以使用 std::call_once 来确保这些初始化工作只被执行一次。

6)插件或模块的加载
在动态加载插件或模块的应用程序中,可以使用 std::call_once 来确保每个插件或模块只被加载和初始化一次。

示例代码:

以下是一个简单的示例,展示了如何使用 std::call_once 和 std::once_flag 来实现单例模式:

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

class Singleton {
public:
    static Singleton& getInstance() {
        std::call_once(instanceFlag, []() { instance = new Singleton(); });
        return *instance;
    }

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

private:
    Singleton() { std::cout << "Singleton instance created." << std::endl; }
    ~Singleton() { delete this; } // 注意:在实际应用中,通常使用智能指针来管理单例的生命周期。

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

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

void worker(int id) {
    Singleton& s = Singleton::getInstance();
    s.doSomething();
}

int main() {
    std::thread t1(worker, 1);
    std::thread t2(worker, 2);

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

    return 0;
}

在这个例子中,Singleton 类有一个私有构造函数和一个静态的 getInstance 方法。getInstance 方法使用 std::call_once 来确保 instance 只被创建一次。即使 worker 函数被多个线程同时调用,Singleton 的实例也只会被创建一次。

5.总结

   std::call_once 是 C++11 提供的模版函数,能够帮助我们在多线程环境中安全、简洁地实现“单次调用”的逻辑。通过合理使用std::call_once,我们可以确保线程安全地执行初始化和其他需要保证只执行一次的操作,从而提高程序的稳定性和效率。正确的使用场景、最佳实践以及性能优化技巧将进一步增强我们在多线程编程中的能力。

推荐阅读

设计模式之单例模式-优快云博客

### 使用 `std::call_once` 创建单例模式 为了确保线程安全并仅初始化一次对象,可以利用 C++11 中引入的 `std::call_once` 函数配合 `std::once_flag` 来实现单例模式。这种方式能够有效防止多线程环境下多次实例化问题的发生。 下面是一个基于上述方法构建的 Singleton 类模板示例: ```cpp #include <iostream> #include <mutex> template<typename T> class Singleton { private: static T* instance; static std::once_flag initFlag; protected: // 构造函数设为保护类型以阻止外部直接创建对象 Singleton() {} public: ~Singleton() {} // 获取唯一实例的方法 static T& GetInstance() { std::call_once(initFlag, [](){ instance = new T(); }); return *instance; } // 删除拷贝构造函数和赋值操作符来保证单一性 Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; }; // 静态成员变量定义与初始化 template<typename T> T* Singleton<T>::instance = nullptr; template<typename T> std::once_flag Singleton<T>::initFlag; int main() { auto& obj = Singleton<MyClass>::GetInstance(); // 进一步的操作... } ``` 此代码片段展示了如何通过静态局部变量以及 lambda 表达式的组合方式,在首次调用 `GetInstance()` 方法时完成类实例的一次性初始化过程[^1]。 同时也删除了复制控制成员(即拷贝构造函数和赋值运算符),从而确保该类不会被意外地重复实例化[^2]。 值得注意的是,这种方法依赖于 C++11 或更新的标准支持;因此对于某些旧版编译器可能无法正常工作[^4]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值