当前位置: 首页 > article >正文

std::call_once的原理及使用

 基本概念   

  std::call_once 是 C++11 中引入的一个模版函数,实现多线程环境下实现单次调用,避免重复执行同一操作。

std::call_once 的原理

  std::call_once 的设计目的是为了保证某个操作在多线程程序中只被调用一次。它结合了std::mutex 和一个称为“标志”(std::once_flag)的机制来标记某个操作是否已经执行过。只有当标志没有被设置时,操作才会被执行。其他线程会等待该操作完成,确保只执行一次,确保在程序的不同线程中,只有第一个调用该操作的线程会执行操作,其他线程会等待直到该操作完成。

std::call_once 的用法

std::call_once 接受两个参数:

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

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

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

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

std::once_flag flag;

void init()
{
    std::cout << "初始化完成!" << std::endl;
}

void thread_func()
{
    std::call_once(flag, init);  // 保证 init只执行一次
}

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;
}

使用场景

线程安全的懒加载

        在多线程环境下,如果需要对某个全局或静态变量进行初始化,而只希望该初始化操作执行一次,std::call_once 是一个理想的选择。它确保只有一个线程执行初始化,其他线程等待,避免重复初始化。

      例如libcURL 库的线程安全初始化,在使用libcURL 进行网络请求时,库的全局初始化操作(如curl_global_init())不是线程安全的,而进行数据请求的操作却是线程安全的。这种情况下,我们可以使用std::call_once 来确保全局初始化只执行一次,无论在哪个线程中调用,也无需担心并发访问。

#include <iostream>
#include <thread>
#include <mutex>
#include <curl/curl.h>

std::once_flag curl_flag;

void initialize_curl()
{
    if (curl_global_init(CURL_GLOBAL_DEFAULT) != 0)
    {
        throwstd::runtime_error("curl_global_init failed!");
    }
    std::cout << "libcURL initialized!" << std::endl;
}

void visite_webSite(const std::string& url)
{
    // 保证 curl_global_init 只被调用一次
    std::call_once(curl_flag, initialize_curl);

    // 创建 CURL 对象
    CURL* curl = curl_easy_init();
    if (curl)
    {
        curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
        CURLcode res = curl_easy_perform(curl);
        if (res == CURLE_OK)
        {
            std::cout << "Fetched data from: " << url << std::endl;
        }
        else
        {
            std::cerr << "CURL error: " << curl_easy_strerror(res) << std::endl;
        }
        curl_easy_cleanup(curl);
    }
}

int main()
{
    std::thread t1(visite_webSite, "https://www.baidu.com");
    std::thread t2(visite_webSite, "https://blog.csdn.net/WSTONECH/article/details");

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

    return 0;
}

资源释放与清理

        在一些应用场景中,你可能希望在线程结束时执行某些清理操作。std::call_once 可确保这些清理工作只会在第一次触发时执行。

确保某些操作的线程安全执行

        如果某些操作需要跨多个线程并且保证线程安全(例如配置设置的初始化、数据库连接池的创建等),std::call_once 提供了一种简单、有效的方式来避免竞争条件。

项目使用注意事项:

   std::call_once 内部实现使用了线程同步机制,如互斥量或其他轻量级同步原语。尽管它确保了操作只执行一次,但如果频繁调用std::call_once,可能会产生一定的性能开销。因此,在性能要求较高的场景中,尽量减少std::call_once 的调用次数,并通过优化同步策略来降低开销。 

          例如libcurl库的全局初始化,没有必要懒加载。如果使用std::call_once写在网络请求的通用函数中固然可行,但是每一次网络访问都会产生同步开销,但是调整代码结构,将库的全局初始化挪到软件初始化的地方(仅调用一次),则软件性能会更好一点。

    例如接入项目中的各种LED、LCD、扫码枪、票箱和相机等等设备大部分需要进行初始化,在之前的老旧项目中初始化和资源释放等直接拷贝设备代码和数据发送写在一个函数中,通过std::call_once进行多线程中初始化和SDK资源释放操作仅执行一次的保证,但是在大型车厂中过车频繁,显示节目多样,会严重拖慢软件的性能;后续优化将设备SDK和动态库初始化转移到软件初始化函数中,资源释放单独写一个全局的软件释放函数,在软件退出时进行统一释放,提高了软件的运行效率。

   一般能通过调整代码结构避免多线程竞争的代码没必要使用std::call_once,如果必须懒加载、必须在多线程中同步,调用频次较低的场景可以考虑使用std::call_once进行。


http://www.kler.cn/a/528744.html

相关文章:

  • [LeetCode]day10 707.设计链表
  • 本地部署DeepSeek
  • 第一个3D程序!
  • 原码、反码、补码以及lowbit运算
  • 指针(C语言)从0到1掌握指针,为后续学习c++打下基础
  • 项目测试之Postman
  • fpga系列 HDL:XILINX Vivado ILA FPGA 在线逻辑分析
  • CF 581A.Vasya the Hipster(Java实现)
  • XML DOM - 访问节点
  • Java线程认识和Object的一些方法ObjectMonitor
  • 基于 STM32 的智能电梯控制系统
  • SZU大学物理2实验报告|超声探伤实验
  • GPG格式介绍:什么是GPG?如何加密和解密?
  • C++哈希(链地址法)(二)详解
  • AI智能化模型助力太阳能光伏板自动巡检运维,基于YOLOv5全系列【n/s/m/l/x】参数模型开发构建无人机航拍场景下太阳能光伏板污损缺陷智能检测识别系统
  • K8S ReplicaSet 控制器
  • Electricity Market Optimization 探索系列(一)
  • 【SQL】SQL注入知识总结
  • 【Java异步编程】CompletableFuture实现:异步任务的合并执行
  • 跨平台的客户端gui到底是选“原生”还是web
  • Vue.js组件开发-实现全屏幻灯片左右切换特效
  • C# 语言基础全面解析
  • 网站快速收录:利用网站导航优化用户体验
  • Pandas基础07(Csv/Excel/Mysql数据的存储与读取)
  • Linux抢占式内核:技术演进与源码解析
  • Cubemx文件系统挂载多设备