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

在多线程环境下实现单例模式,核心问题是如何保证在并发访问时只有一个实例被创建,并且不会出现竞态条件。下面介绍几种常用且成熟的实现方式,帮助你在实际项目中快速部署线程安全的单例。

1. C++11 之后的本地静态变量(Meyers 单例)

class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance;   // C++11 起保证线程安全
        return instance;
    }
    // 禁止复制构造和赋值操作
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
    ~Singleton() = default;
};
  • 优点:代码简洁,自动销毁,适用于所有 C++11 及以后编译器。
  • 缺点:实例化延迟到第一次调用,无法提前初始化。

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

class Singleton {
public:
    static Singleton* instance() {
        if (instance_ == nullptr) {               // 第一次检查
            std::lock_guard<std::mutex> lock(mutex_);
            if (instance_ == nullptr) {           // 第二次检查
                instance_ = new Singleton();
            }
        }
        return instance_;
    }
    ~Singleton() {
        delete instance_;
    }

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

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

Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
  • 优点:延迟实例化,避免不必要的锁开销。
  • 缺点:实现复杂,需要保证内存可见性(C++11 std::atomicvolatile)。如果实现不当,可能导致线程安全问题。

3. 枚举单例(Java 风格,C++ 适用)

C++ 11 的枚举类型可用于实现单例,利用枚举的内部静态存储特性:

class Singleton {
public:
    static Singleton& get() {
        enum Helper { dummy = Singleton::instance() };
        return instance();
    }
    // ...
private:
    static Singleton& instance() {
        static Singleton instance;
        return instance;
    }
};
  • 优点:确保实例在第一次使用时创建且唯一。
  • 缺点:较少人使用,代码可读性略差。

4. std::call_once 与 std::once_flag

class Singleton {
public:
    static Singleton& instance() {
        std::call_once(flag_, []() { instance_ = new Singleton(); });
        return *instance_;
    }
    ~Singleton() { delete instance_; }

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

    static Singleton* instance_;
    static std::once_flag flag_;
};

Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::flag_;
  • 优点:利用标准库提供的一次性初始化机制,简单且安全。
  • 缺点:需要手动删除实例(可在 atexit 注册析构)。

5. 线程安全的懒加载与销毁

在某些场景下,你可能需要在程序结束前显式销毁单例,以释放资源。可以结合 std::atexit

class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance;
        std::atexit(&destroy);
        return instance;
    }

private:
    static void destroy() {
        // 释放资源
    }
};

6. 小结

  • 推荐:如果你使用的是 C++11 或更高版本,最简洁可靠的方式是 Meyers Singleton(局部静态变量)。
  • 需要手动销毁:如果你想在程序结束前释放资源,考虑 std::call_onceatexit
  • 跨平台兼容:上述实现都基于 C++ 标准库,几乎在所有主流编译器(GCC, Clang, MSVC)上都能安全工作。

通过上述方法,你可以根据具体项目需求,选择最合适的单例实现,既保证线程安全,又保持代码简洁。祝你编码愉快!

发表评论