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

在多线程环境下,单例模式需要保证只有一个实例,并且在多个线程并发访问时不产生竞态条件。C++11 引入了线程安全的静态局部变量初始化,利用这一特性可以轻松实现线程安全的单例。下面给出几种常见实现方式,并对它们的优缺点做简要比较。

1. Meyers单例(C++11 之后)

class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance;   // 线程安全初始化
        return instance;
    }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
private:
    Singleton() {}   // 私有构造
};

优点

  • 代码最简洁,使用标准库即可。
  • 编译器保证线程安全的初始化,性能优秀。
  • 延迟加载:实例仅在第一次访问时创建。

缺点

  • 不能在运行时销毁实例(如果需要在程序结束后释放资源,可在程序末尾手动调用 instance().~Singleton() 或使用 std::unique_ptr 包装)。
  • 需要 C++11 或更高版本。

2. 带互斥锁的懒汉式(适用于 C++11 之前)

#include <mutex>

class Singleton {
public:
    static Singleton* getInstance() {
        if (!instance_) {
            std::lock_guard<std::mutex> lock(mutex_);
            if (!instance_) {
                instance_ = new Singleton();
            }
        }
        return instance_;
    }
    ~Singleton() { delete instance_; }
private:
    Singleton() {}
    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++(如 C++03)。
  • 可以手动控制实例的销毁。

缺点

  • 双重检查锁(Double-Checked Locking)在某些编译器/平台上仍可能存在可见性问题。
  • 额外的锁开销,即使实例已创建后仍需检查。

3. 使用 std::call_once

#include <mutex>

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

Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;

优点

  • std::call_once 保证一次性初始化,线程安全且效率高。
  • std::mutex 的双重检查相比更简洁。

缺点

  • 需要手动销毁实例;若不销毁,可能在 atexit 期间出现已销毁后仍被访问的问题。

4. 静态唯一实例(带手动销毁)

class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance;
        return instance;
    }
    static void destroy() {
        // 必须先把引用计数降到0或手动销毁
    }
private:
    Singleton() {}
};

此方式适用于想在运行时销毁单例,但仍想保持线程安全。

总结

  • 最推荐:如果使用 C++11 及以上,Meyers 单例是最简洁、性能最佳的选择。
  • 兼容旧版:若项目需兼容 C++03,使用 std::mutex + 双重检查或 std::call_once(需手动实现)是可行方案。
  • 需要手动销毁:使用 std::call_onceMeyers 并在程序退出前手动销毁实例。

在实际项目中,建议先评估编译器支持、性能需求和资源管理需求,然后选用最合适的实现方式。

发表评论