【C/C++】线程安全初始化:std::call_once详解

[复制链接]
发表于 2025-7-7 21:16:39 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
std::call_once 使用详解

std::call_once 是 C++11 尺度库中提供的一个线程安全的一次性调用机制,位于 <mutex> 头文件中。它确保某个可调用对象只被实行一次,纵然多个线程同时实验调用它。
基本用法
  1. #include <mutex>
  2. #include <thread>
  3. std::once_flag flag; // 全局或静态的once_flag
  4. void initialize() {
  5.     // 初始化代码(只执行一次)
  6. }
  7. void thread_function() {
  8.     std::call_once(flag, initialize);
  9. }
  10. int main() {
  11.     std::thread t1(thread_function);
  12.     std::thread t2(thread_function);
  13.     t1.join();
  14.     t2.join();
  15. }
复制代码
焦点组件


  • std:nce_flag

    • 轻量级对象,用于标志函数是否已被调用
    • 必须黑白局部的(全局/静态/类静态成员)
    • 不可复制、不可移动、不可赋值

  • std::call_once

    • 函数模板:template <class Callable, class... Args> void call_once(once_flag& flag, Callable&& func, Args&&... args);
    • 保证 func 只实行一次
    • 线程安全:其他线程会壅闭直到初始化完成

关键特性


  • 线程安全保证

    • 只有一个线程会实行函数
    • 其他线程会壅闭直到函数实行完成
    • 实行完成后,所有线程都能看到初始化效果

  • 非常处理惩罚

    • 如果函数抛出非常,非常会流传给调用者
    • once_flag 不会被标志为完成,其他线程会重试实行
    • 需要确保函数在重试时能成功

  • 性能特点

    • 初始化完成后只有原子检查的开销
    • 初始化期间其他线程会壅闭
    • 比双重检查锁定更简单安全

使用场景

1. 延迟初始化(Lazy Initialization)
  1. class ExpensiveResource {
  2. public:
  3.     static ExpensiveResource& getInstance() {
  4.         static std::once_flag initFlag;
  5.         std::call_once(initFlag, [] {
  6.             instance.reset(new ExpensiveResource());
  7.         });
  8.         return *instance;
  9.     }
  10. private:
  11.     static std::unique_ptr<ExpensiveResource> instance;
  12.     ExpensiveResource() { /* 耗时的初始化 */ }
  13. };
复制代码
延迟初始化,使用static Singleton instance; return instance;形式更优
2. 线程安全的单例模式
  1. class Logger {
  2. public:
  3.     static Logger& getInstance() {
  4.         static std::once_flag flag;
  5.         std::call_once(flag, [] {
  6.             instance.reset(new Logger());
  7.         });
  8.         return *instance;
  9.     }
  10.     void log(const std::string& message) {
  11.         std::lock_guard<std::mutex> lock(mutex);
  12.         // 线程安全的日志日志记录
  13.     }
  14. private:
  15.     static std::unique_ptr<Logger> instance;
  16.     std::mutex mutex;
  17.     Logger() = default;
  18. };
复制代码
3. 初始化共享资源
  1. class DatabaseConnection {
  2. public:
  3.     static DatabaseConnection& getConnection() {
  4.         static std::once_flag initFlag;
  5.         std::call_once(initFlag, &DatabaseConnection::init, this);
  6.         return *this;
  7.     }
  8. private:
  9.     void init() {
  10.         // 建立数据库连接
  11.     }
  12. };
复制代码
4. 一次性配置加载
  1. class Config {
  2. public:
  3.     static const Config& load() {
  4.         static std::once_flag flag;
  5.         static Config instance;
  6.         std::call_once(flag, [] {
  7.             instance.loadFromFile("config.json");
  8.         });
  9.         return instance;
  10.     }
  11. private:
  12.     void loadFromFile(const std::string& path) {
  13.         // 从文件加载配置
  14.     }
  15. };
复制代码
5. 插件体系初始化
  1. class PluginManager {
  2. public:
  3.     void initialize() {
  4.         std::call_once(initFlag, [this] {
  5.             loadPlugins();
  6.             registerHooks();
  7.             initEventSystem();
  8.         });
  9.     }
  10. private:
  11.     std::once_flag initFlag;
  12. };
复制代码
与局部静态变量的对比

特性std::call_once局部静态变量 (C++11+)语法复杂度较复杂简单控制粒度精细控制整个函数多位置调用支持多个位置调用雷同初始化只能在一个位置初始化成员函数初始化可直接用于成员函数只能用于静态成员或全局函数初始化参数可传递参数无参数多次初始化差异函数支持不支持性能初始化后开销小雷同非常处理惩罚显式处理惩罚,可重试编译器处理惩罚最佳实践


  • 只用于真正的"一次性"操纵:不要用于大概多次初始化的场景
  • 避免在性能关键路径使用:初始化期间会壅闭其他线程
  • 确保函数幂等性:纵然多次调用也不会产生副作用(考虑非常情况)
  • 配合智能指针管理资源:避免资源泄漏
  • 谨慎处理惩罚非常:确保在重试时能成功完成初始化
  • 避免递归调用:不要在call_once的函数内再次调用call_once
错误用法示例
  1. // 错误1:局部once_flag(每次调用都会新建)
  2. void unsafe_init() {
  3.     std::once_flag flag; // 错误!每次调用都是新的flag
  4.     std::call_once(flag, []{ /* ... */ });
  5. }
  6. // 错误2:尝试多次初始化
  7. void double_init() {
  8.     static std::once_flag flag;
  9.     std::call_once(flag, []{ /* 初始化A */ });
  10.     std::call_once(flag, []{ /* 初始化B */ }); // 永远不会执行
  11. }
  12. // 错误3:异常处理不当
  13. void risky_init() {
  14.     static std::once_flag flag;
  15.     std::call_once(flag, []{
  16.         if (/* 条件 */) {
  17.             throw std::runtime_error("Oops");
  18.         }
  19.     }); // 抛出异常后flag未标记,其他线程会重试
  20. }
复制代码
总结

std::call_once 是 C++ 中实现线程安全一次性初始化的强大工具,特别适用于:

  • 延迟初始化昂贵资源
  • 实现线程安全的单例
  • 加载配置或资源
  • 初始化共享状态
相比于传统的双重检查锁定模式,std::call_once 提供了更简便、更安全的替代方案,避免了复杂的同步逻辑和潜伏的内存排序题目。在 C++11 及以上情况中,它是实现线程安全初始化的紧张工具。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告
回复

使用道具 举报

快速回复 返回顶部 返回列表