导读:本期聚焦于小伙伴创作的《C++ std::call_once怎么用?如何保证多线程下只执行一次》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C++ std::call_once怎么用?如何保证多线程下只执行一次》有用,将其分享出去将是对创作者最好的鼓励。

在多线程开发中,很多场景需要保证某个操作只执行一次,比如全局配置初始化、单例对象创建等,C++标准库提供的std::call_once就是专门应对这类需求的工具,它能在多线程竞争的情况下确保目标函数仅被调用一次。

C++ std::call_once怎么用?如何保证多线程下只执行一次

std::call_once基本用法

使用std::call_once需要配合std::once_flag类型的标记,once_flag对象需要在所有线程可见的作用域中定义,通常作为全局变量或者类的静态成员。std::call_once的函数原型如下:

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

// 定义once_flag标记,必须是全局或者静态的,不能被拷贝
std::once_flag init_flag;

// 需要单次执行的函数
void init_config() {
    std::cout << "执行配置初始化操作" << std::endl;
}

void worker_thread() {
    // 调用call_once,传入标记和要执行的函数
    std::call_once(init_flag, init_config);
}

int main() {
    // 创建5个线程同时执行worker_thread
    std::thread t1(worker_thread);
    std::thread t2(worker_thread);
    std::thread t3(worker_thread);
    std::thread t4(worker_thread);
    std::thread t5(worker_thread);

    t1.join();
    t2.join();
    t3.join();
    t4.join();
    t5.join();
    return 0;
}

上述代码中,5个线程同时调用worker_thread,但是init_config函数只会执行一次,其他线程调用std::call_once时会直接跳过执行,不会重复触发初始化逻辑。

std::call_once的实战场景:单例模式实现

单例模式是std::call_once最典型的应用场景,传统的单例实现需要考虑多线程下的竞态问题,使用std::call_once可以简化实现逻辑,同时保证线程安全。

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

class Singleton {
private:
    Singleton() {
        std::cout << "单例对象创建" << std::endl;
    }
    static std::once_flag init_flag;
    static Singleton* instance;

public:
    // 禁止拷贝和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* get_instance() {
        // 保证创建单例的代码只执行一次
        std::call_once(init_flag, []() {
            instance = new Singleton();
        });
        return instance;
    }
};

// 静态成员初始化
std::once_flag Singleton::init_flag;
Singleton* Singleton::instance = nullptr;

void test_thread() {
    Singleton* s = Singleton::get_instance();
    std::cout << "线程获取单例地址: " << s << std::endl;
}

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

    t1.join();
    t2.join();
    t3.join();
    return 0;
}

运行上述代码可以看到,单例的构造函数只会执行一次,所有线程获取到的单例地址都是同一个,避免了多线程下重复创建单例对象的问题。

使用std::call_once的注意事项

  • once_flag对象不能被拷贝或者赋值,否则会导致标记失效,无法保证单次执行。通常once_flag需要定义为全局变量、静态变量或者类静态成员。
  • 如果std::call_once调用的函数抛出异常,那么这次调用会被视为未完成,其他线程会再次尝试调用该函数,直到某次调用成功执行。
  • std::call_once的第二个参数可以是普通函数、函数对象、lambda表达式,只要可调用即可,支持传入参数,参数需要跟在可调用对象之后传入。
  • once_flag的生命周期必须长于所有调用std::call_once的线程,否则会出现未定义行为。

std::call_once和普通加锁的区别

很多开发者可能会想到用互斥锁来实现单次执行,比如先加锁判断标记再执行,但是这种方式需要每次都加锁判断,而std::call_once在第一次执行完成后,后续调用不需要加锁,性能开销更低。另外std::call_once已经处理了所有竞态情况,不需要开发者自己处理判断逻辑,代码更简洁,也不容易出错。

// 普通加锁实现单次执行的示例
#include <mutex>
#include <iostream>

std::mutex mtx;
bool is_inited = false;

void init_with_lock() {
    std::lock_guard<std::mutex> lock(mtx);
    if (!is_inited) {
        std::cout << "执行初始化" << std::endl;
        is_inited = true;
    }
}

对比可以看出,std::call_once的实现逻辑更简洁,不需要手动维护初始化标记和加锁逻辑,更适合单次执行的场景。

C++std::call_once多线程once_flag修改时间:2026-06-13 23:45:39

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。