自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

只執(zhí)行一次,不多不少!C++11 的線程安全神器 std::once_flag 與 call_once 詳解

開發(fā)
今天咱們聊一個看起來很小但實際超級實用的話題 —— C++11中的std::once_flag和call_once

今天咱們聊一個看起來很小但實際超級實用的話題 —— C++11中的std::once_flag和call_once。聽起來很高大上?別擔(dān)心,我保證用最接地氣的方式給你講明白,絕不玩文縐縐的!

一個生活中的小故事

想象一下這個場景:你和三個朋友一起住,早上大家都要用洗衣機。如果每個人都去按一次洗衣機的"開始"按鈕會發(fā)生什么?對,洗衣機會被重啟好幾次,衣服永遠洗不完!

理想情況是:無論誰先到洗衣機前,只需要有一個人按一次開始按鈕就夠了。其他人看到洗衣機已經(jīng)在運轉(zhuǎn),就不需要再按了。

這就是我們今天要講的std::once_flag和call_once要解決的問題!

為什么需要"只執(zhí)行一次"的機制?

在多線程程序中,有些初始化工作我們希望:

  • 必須執(zhí)行(不能少)
  • 只能執(zhí)行一次(不能多)
  • 所有線程都要等這個初始化完成后才能繼續(xù)

最典型的例子就是單例模式(Singleton Pattern):無論多少個線程同時請求,系統(tǒng)中某個對象只能有一個實例。

沒有call_once之前,大家都這么寫:

// 傳統(tǒng)寫法,容易出問題
class Singleton {
private:
    static Singleton* instance;
    staticstd::mutex mutex;
    
    Singleton() { /* 構(gòu)造函數(shù) */ }
    
public:
    static Singleton* getInstance() {
        if (instance == nullptr) {  // 第一次檢查
            std::lock_guard<std::mutex> lock(mutex);
            if (instance == nullptr) {  // 第二次檢查
                instance = new Singleton();
            }
        }
        return instance;
    }
};

看看這個"雙重檢查鎖定"模式,又臭又長,還容易寫錯。更糟的是,在某些內(nèi)存模型下,這段代碼仍然可能有問題!

救星來了:std::once_flag 和 call_once

C++11引入了兩個救星:

  • std::once_flag:一面"只執(zhí)行一次"的旗幟
  • std::call_once:確保某個函數(shù)在多線程環(huán)境下只執(zhí)行一次的工具

讓我們看看它們是怎么用的:

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

std::once_flag flag;  // 創(chuàng)建一個"只執(zhí)行一次"的旗幟

void do_once() {
    std::cout << "這段代碼只會執(zhí)行一次!" << std::endl;
}

void thread_func() {
    // 無論多少線程調(diào)用這個函數(shù),do_once()只會執(zhí)行一次
    std::call_once(flag, do_once);
    std::cout << "線程 " << std::this_thread::get_id() << " 執(zhí)行完畢" << 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;
}

運行這段代碼,你會發(fā)現(xiàn)"這段代碼只會執(zhí)行一次!"這句話確實只打印了一次,而"線程xx執(zhí)行完畢"會打印三次。

工作原理:它是怎么做到的?

std::once_flag和call_once背后的工作原理其實很簡單:

  • once_flag是一個標(biāo)記,記錄關(guān)聯(lián)的函數(shù)是否已經(jīng)執(zhí)行過
  • 當(dāng)?shù)谝粋€線程調(diào)用call_once時,它會執(zhí)行指定的函數(shù)
  • 其他線程調(diào)用同一個call_once(使用同一個flag)時,會等待第一個線程執(zhí)行完畢
  • 一旦函數(shù)執(zhí)行完成,所有線程都會繼續(xù)運行,但函數(shù)不會被重復(fù)執(zhí)行

單例模式的優(yōu)秀實踐

現(xiàn)在,讓我們用std::call_once來改寫單例模式:

class Singleton {
private:
    static std::once_flag init_flag;
    static Singleton* instance;
    
    Singleton() { 
        std::cout << "創(chuàng)建單例對象!" << std::endl;
    }
    
public:
    static Singleton* getInstance() {
        std::call_once(init_flag, []() {
            instance = new Singleton();
        });
        return instance;
    }
    
    void doSomething() {
        std::cout << "單例正在工作..." << std::endl;
    }
};

// 在類外初始化靜態(tài)成員
std::once_flag Singleton::init_flag;
Singleton* Singleton::instance = nullptr;

是不是簡潔了很多?沒有復(fù)雜的雙重檢查,沒有手動管理互斥鎖,代碼量減少了一半!

更多實際應(yīng)用場景

除了單例模式,std::call_once還適用于很多場景:

  • 延遲初始化昂貴資源
class DatabaseConnection {
private:
    std::once_flag conn_init;
    Connection* conn;
    
public:
    Connection* getConnection() {
        std::call_once(conn_init, [this]() {
            std::cout << "建立數(shù)據(jù)庫連接(耗時操作)..." << std::endl;
            conn = new Connection("db://example");
        });
        return conn;
    }
};
  • 線程池的一次性初始化:
class ThreadPool {
private:
    std::once_flag init_flag;
    std::vector<std::thread> threads;
    
public:
    void initialize(size_t thread_count) {
        std::call_once(init_flag, [this, thread_count]() {
            for (size_t i = 0; i < thread_count; ++i) {
                threads.emplace_back(&ThreadPool::worker_thread, this);
            }
        });
    }
    
    void worker_thread() {
        // 線程工作內(nèi)容...
    }
};
  • 配置文件的一次性加載:
class Configuration {
private:
    std::once_flag load_flag;
    std::map<std::string, std::string> settings;
    
public:
    std::string getSetting(const std::string& key) {
        std::call_once(load_flag, [this]() {
            std::cout << "加載配置文件..." << std::endl;
            // 加載配置到settings映射表...
        });
        return settings[key];
    }
};

比較:call_once vs 其他線程安全方式

那么,std::call_once比其他方法好在哪里呢?

  • 對比靜態(tài)局部變量:C++11保證靜態(tài)局部變量的初始化是線程安全的,但call_once更靈活,可以在任何地方調(diào)用,不限于函數(shù)作用域
  • 對比互斥鎖:比手動管理互斥鎖更簡潔,不容易出錯
  • 對比原子操作:比使用std::atomic編寫復(fù)雜的初始化邏輯更清晰
  • 性能優(yōu)勢:在多數(shù)實現(xiàn)中,call_once采用了類似讀寫鎖的優(yōu)化,多線程并發(fā)時性能更好

容易踩的坑

使用std::call_once雖然簡單,但還是有一些坑需要注意:

  • 一個flag只能用于一次初始化:如果你需要多個不同的一次性初始化,就需要多個once_flag
  • 異常處理:如果被調(diào)用的函數(shù)拋出異常,call_once會認(rèn)為初始化失敗,下次還會嘗試執(zhí)行
  • 避免死鎖:不要在call_once調(diào)用的函數(shù)中再次使用同一個once_flag,否則會死鎖
  • 線程退出:once_flag不會在線程退出時自動重置,它是全局狀態(tài)

來看一個異常處理的例子:

std::once_flag flag;

void may_throw() {
    std::call_once(flag, []() {
        std::cout << "嘗試初始化..." << std::endl;
        throw std::runtime_error("初始化失敗!");
    });
}

int main() {
    try {
        may_throw();  // 第一次嘗試初始化,會拋出異常
    } catch (const std::exception& e) {
        std::cout << "捕獲異常: " << e.what() << std::endl;
    }
    
    try {
        may_throw();  // 由于上次失敗,會再次嘗試初始化
    } catch (const std::exception& e) {
        std::cout << "再次捕獲異常: " << e.what() << std::endl;
    }
    
    return 0;
}

輸出結(jié)果會顯示初始化被嘗試了兩次!

總結(jié):什么時候用call_once?

std::once_flag和call_once最適合以下場景:

  • 需要線程安全的一次性初始化
  • 單例模式的實現(xiàn)
  • 共享資源的延遲初始化
  • 需要確保某個操作在多線程環(huán)境下只執(zhí)行一次

記住,它是C++11標(biāo)準(zhǔn)庫給我們提供的"只執(zhí)行一次"的保證,遠比我們自己實現(xiàn)雙重檢查鎖定更可靠、更簡單。

責(zé)任編輯:趙寧寧 來源: 跟著小康學(xué)編程
相關(guān)推薦

2023-11-21 21:59:50

c++接口

2022-02-10 07:41:02

JavaScriponce函數(shù)

2024-06-05 11:06:22

Go語言工具

2023-06-06 08:28:58

Sync.OnceGolang

2016-12-06 09:34:33

線程框架經(jīng)歷

2013-05-30 00:49:36

C++11C++條件變量

2021-02-01 08:41:45

Flink語義數(shù)據(jù)

2013-07-31 11:09:05

C++11

2021-06-02 07:07:09

Flink處理語義

2024-02-21 23:43:11

C++11C++開發(fā)

2023-09-22 22:27:54

autoC++11

2020-09-23 16:31:38

C++C++11啟動線程

2020-12-09 10:55:25

ArrayvectorLinux

2024-10-10 10:32:04

2011-08-19 09:41:56

C++

2021-08-29 18:13:03

緩存失效數(shù)據(jù)

2024-05-29 13:21:21

2025-04-07 01:11:00

右值C++泛型

2020-06-01 21:07:33

C11C++11內(nèi)存

2024-04-28 08:38:53

Kafka分布式系統(tǒng)
點贊
收藏

51CTO技術(shù)棧公眾號