首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

C++ 單例模式線程安全實現全攻略:從懶漢到 C++11 call_once

     編輯:程式設計綜合問答

單例模式在多線程環境下的核心挑戰

單例模式要求全局僅有一個實例,且提供全局訪問點。多線程下的難點在於:防止多線程並發創建多個實例,同時保證性能(避免每次獲取都加鎖)。

五種經典實現對比

1. 餓漢式——最簡單、天然線程安全

class Singleton {
    static Singleton instance; // 靜態存儲區,程序啟動時構造
public:
    static Singleton& GetInstance() { return instance; }
    // 禁用拷貝/移動
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
private:
    Singleton() = default;
};
Singleton Singleton::instance;

優點:無鎖、無競態、編譯器保證初始化順序。
缺點:程序啟動即構造,即使從未使用也佔用資源;構造依賴其他單例時可能發生「靜態初始化順序災難」。

2. 懶漢式 + 雙重檢查鎖 (DCLP) —— C++11 前的主流寫法

class Singleton {
    static Singleton* instance;
    static std::mutex mtx;
public:
    static Singleton* GetInstance() {
        if (instance == nullptr) {           // 第一次檢查(無鎖)
            std::lock_guard lk(mtx);
            if (instance == nullptr) {       // 第二次檢查(持鎖)
                instance = new Singleton();
            }
        }
        return instance;
    }
    // ... 禁用拷貝、私有構造
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

關鍵:instance 必須聲明為 std::atomic<Singleton*> 並配合 memory_order_acquire/release,否則 CPU 亂序執行可能導致其他線程看到半初始化的對象(指針非空但構造函數未執行完)。C++11 前極易寫錯。

3. C++11 局部靜態變量 —— 推薦標準寫法

class Singleton {
public:
    static Singleton& GetInstance() {
        static Singleton instance; // C++11 保證線程安全初始化(magic static)
        return instance;
    }
    // ... 禁用拷貝、私有構造
private:
    Singleton() = default;
};

優點:代碼極簡、零鎖開銷(首次初始化內部用原子操作)、按需構造、自動析構、彌補了靜態初始化順序問題。
缺點:析構順序仍為逆構造順序,若析構時依賴其他單例需謹慎。

4. std::call_once —— 顯式控制初始化邏輯

class Singleton {
    static std::unique_ptr<Singleton> instance;
    static std::once_flag flag;
public:
    static Singleton* GetInstance() {
        std::call_once(flag, []{ instance.reset(new Singleton()); });
        return instance.get();
    }
    // ...
};
std::unique_ptr<Singleton> Singleton::instance;
std::once_flag Singleton::flag;

適用於初始化邏輯複雜(如需讀取配置、連接數據庫)且必須保證只執行一次的場景。

5. 模板化單例基類(CRTP)—— 復用單例邏輯

template <typename T>
class SingletonBase {
public:
    static T& Instance() {
        static T instance; // 派生類構造
        return instance;
    }
protected:
    SingletonBase() = default;
    ~SingletonBase() = default;
    SingletonBase(const SingletonBase&) = delete;
    SingletonBase& operator=(const SingletonBase&) = delete;
};

// 使用
class MyService : public SingletonBase<MyService> {
    friend class SingletonBase<MyService>;
    MyService() { /* 初始化 */ }
};

書籍推薦:深入掌握 C++ 多線程

  • 《C++ Concurrency in Action》Anthony Williams 著(中文版《C++ 并发编程实战》):C++11/14/17/20 多線程聖經,詳細講解 memory model、atomic、lock-free、future、actor 模型等。
  • 《Effective Modern C++》Scott Meyers 著(項目 14-17 專講線程安全初始化)。
  • 侯捷《Win32 多核程序設計》:側重 Windows 平台原生 API(CreateThread, IOCP, SRW Lock, Condition Variable),適合需深度綁定 Windows 系統編程的開發者。
  • 《Linux 多線程服務端編程》陳碩著:基於 muduo 網絡庫,講解 Reactor 模式、線程池、無鎖隊列實戰。

總結建議

現代 C++(C++11 及以上)項目,**首選「局部靜態變量」寫法**(方案 3),既標準又高效。僅當初始化邏輯極其複雜或需延遲到首次調用時顯式報錯時,才考慮 std::call_once。請堅決拋棄手寫 DCLP,除非你精通 C++ 內存模型並能正確使用 std::atomic 與內存序。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved