如何在C++中实现线程安全的单例模式?

在多线程环境下,单例模式的实现需要保证同一时刻只有一个实例被创建,并且在所有线程中共享该实例。下面给出几种常见的实现方式,并结合 C++17 及其后的标准进行说明。

1. 静态局部变量 + std::call_once

C++11 以后,局部静态变量的初始化是线程安全的,但如果想更显式地控制一次性初始化,可以配合 std::call_once 使用。

#include <mutex>

class Singleton {
public:
    static Singleton& instance() {
        std::call_once(initFlag_, []() {
            instance_.reset(new Singleton);
        });
        return *instance_;
    }

    // 业务接口
    void doSomething() { /* ... */ }

private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static std::unique_ptr <Singleton> instance_;
    static std::once_flag initFlag_;
};

std::unique_ptr <Singleton> Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;

优点

  • 明确一次性初始化的语义。
  • std::call_once 只在第一次调用时执行一次,后续调用几乎无开销。

2. 带懒加载的局部静态变量

C++11 之后,局部静态变量的初始化是线程安全的。利用这一特性可以写出最简洁的单例。

class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance; // 线程安全的懒加载
        return instance;
    }

    void doSomething() { /* ... */ }

private:
    Singleton() = default;
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

优点

  • 代码最短,易于阅读。
  • 只需要一次内存分配,且初始化完成后不需要额外同步。

3. Meyers 单例(C++11 版)

Meyers 单例就是上述第二种实现方式的命名,因其由 Scott Meyers 提倡而得名。它利用局部静态变量实现懒加载和线程安全,已经成为 C++ 社区的标准做法。

4. 双重检查锁(Double-Check Locking)

虽然在 C++11 之前使用双重检查锁能实现线程安全单例,但在现代 C++ 中已不推荐使用。原因是它的实现依赖于内存模型的细节,容易出错。

class Singleton {
public:
    static Singleton* instance() {
        if (!instance_) {
            std::lock_guard<std::mutex> lock(mutex_);
            if (!instance_) {
                instance_ = new Singleton();
            }
        }
        return instance_;
    }

private:
    static Singleton* instance_;
    static std::mutex mutex_;
};

注意

  • 必须保证 instance_ 的初始化对所有线程可见。
  • 如果不使用 volatilestd::atomic,可能出现指针可见性问题。

5. 预先初始化(Eager Initialization)

如果实例创建成本低且不需要延迟加载,可以直接在静态成员中初始化。

class Singleton {
public:
    static Singleton& instance() {
        return instance_;
    }

private:
    static Singleton instance_;
};

Singleton Singleton::instance_;

优点

  • 简单直观。
  • 对于构造函数不抛异常的类非常安全。

6. 线程安全的惰性加载与销毁

如果需要在程序结束时显式销毁单例,可结合 std::unique_ptrstd::atexit

class Singleton {
public:
    static Singleton& instance() {
        std::call_once(initFlag_, []() {
            ptr_.reset(new Singleton);
            std::atexit(&Singleton::destroy);
        });
        return *ptr_;
    }

private:
    static void destroy() { ptr_.reset(); }

    static std::unique_ptr <Singleton> ptr_;
    static std::once_flag initFlag_;
};

这样可以确保单例在程序退出前被正确销毁,避免潜在的资源泄漏。

小结

  • 最推荐:C++11 之后使用局部静态变量实现的 Meyers 单例,既简洁又线程安全。
  • 若需要更细粒度的控制或日志,可使用 std::call_once
  • 避免双重检查锁和手动同步,除非你必须兼容旧标准或有特殊性能需求。

选择哪种实现方式,取决于项目的需求、编译器支持以及对代码可读性的要求。祝你编码愉快!

发表评论