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

在多线程环境下,单例模式需要确保只有一个实例存在,并且在任何时刻都可以安全地访问该实例。下面给出几种常见的实现方式,并对其优缺点进行简要说明。

1. 局部静态变量(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;
};
  • 优点:实现简洁,编译器保证线程安全;延迟初始化(第一次调用时创建)。
  • 缺点:无法控制实例的销毁时机(在程序退出时由系统负责)。

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

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

    // 禁止拷贝构造和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
    static std::atomic<Singleton*> instance_;
    static std::mutex mutex_;
};

std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;
  • 优点:只有在首次创建实例时才加锁,后续访问更快。
  • 缺点:实现复杂,易出错;需要 C++11 原子和内存序保证。

3. 静态成员指针 + 互斥量 + std::call_once

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

    // 禁止拷贝构造和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
    static Singleton* instance_;
    static std::once_flag flag_;
};

Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::flag_;
  • 优点:代码更安全,避免了手动锁;兼容 C++11 及以后。
  • 缺点:需要手动管理实例的销毁,通常可通过 atexit 或者 std::unique_ptr 自动释放。

4. 使用 std::shared_ptrstd::weak_ptr

如果单例需要按需销毁,可以使用 std::shared_ptrstd::weak_ptr 组合:

class Singleton {
public:
    static std::shared_ptr <Singleton> instance() {
        std::lock_guard<std::mutex> lock(mutex_);
        if (auto sp = ptr_.lock()) {
            return sp;
        }
        auto sp = std::shared_ptr <Singleton>(new Singleton);
        ptr_ = sp;
        return sp;
    }

    // 禁止拷贝构造和赋值
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() = default;
    static std::weak_ptr <Singleton> ptr_;
    static std::mutex mutex_;
};

std::weak_ptr <Singleton> Singleton::ptr_;
std::mutex Singleton::mutex_;
  • 优点:实例可被销毁后再次创建,资源更灵活。
  • 缺点:实现更复杂,性能略低。

小结

  • 对于大多数 C++11 及以后项目,Meyers单例(局部静态变量)已足够,代码简洁且线程安全。
  • 若需要更细粒度的控制或想延迟销毁,推荐使用 std::call_oncestd::shared_ptr 方案。
  • 双重检查锁虽然理论上更快,但在 C++11 的内存模型下实现复杂且不推荐使用。

选择合适的实现方式,既能保证线程安全,又能满足项目的资源管理需求。

发表评论