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

在多线程环境下,单例模式需要保证只有一个实例,并且在并发访问时不产生竞争。C++11 以后,标准提供了原子操作、内存序列化以及线程安全的静态局部变量初始化等特性,使实现线程安全的单例变得更简单。下面给出几种常见实现方式,并对比其优缺点。

1. 基于局部静态变量的懒汉式

class Singleton {
public:
    static Singleton& instance() {
        static Singleton instance;   // C++11 规定此初始化是线程安全的
        return instance;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() {}
};

优点

  • 代码简洁,几行即可实现。
  • 只在第一次调用时进行初始化,后续访问几乎没有开销。
  • 编译器保证线程安全,不需要显式加锁。

缺点

  • 如果 Singleton 的构造抛出异常,后续访问会再次尝试构造,可能导致多次构造失败。
  • 无法控制实例销毁的时机(在程序结束时由系统回收)。

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

class Singleton {
public:
    static Singleton* getInstance() {
        Singleton* temp = instance_.load(std::memory_order_acquire);
        if (!temp) {
            std::lock_guard<std::mutex> lock(mutex_);
            temp = instance_.load(std::memory_order_relaxed);
            if (!temp) {
                temp = new Singleton();
                instance_.store(temp, std::memory_order_release);
            }
        }
        return temp;
    }

    // 其他成员

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

std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;

优点

  • 只在第一次实例化时加锁,后续访问不需要加锁,性能更好。
  • 可以在需要时手动销毁实例(通过 delete)。

缺点

  • 代码复杂,容易出现错误(例如忘记使用 memory_order)。
  • 在旧编译器或未实现强内存模型的实现上可能不安全。

3. std::call_oncestd::once_flag

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

    // 其他成员

private:
    Singleton() {}
    static Singleton* instance_;
    static std::once_flag initFlag_;
};

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

优点

  • 代码可读性好,call_once 把锁的细节封装了。
  • 保证单次初始化,线程安全。

缺点

  • 仍然使用裸指针,需要手动销毁。
  • 需要配合 std::unique_ptr 或在 atexit 中销毁。

4. std::unique_ptrstd::shared_ptr

class Singleton {
public:
    static std::shared_ptr <Singleton> getInstance() {
        std::call_once(initFlag_, []() {
            instance_.reset(new Singleton());
        });
        return instance_;
    }

private:
    Singleton() {}
    static std::shared_ptr <Singleton> instance_;
    static std::once_flag initFlag_;
};

std::shared_ptr <Singleton> Singleton::instance_;
std::once_flag Singleton::initFlag_;

优点

  • 自动内存管理,避免手动 delete
  • 可通过 shared_ptr 的引用计数实现延迟销毁。

缺点

  • 引入额外的 shared_ptr 运行时成本。
  • 需要注意循环引用问题。

5. 模板化单例(对多类型共用同一实现)

template <typename T>
class Singleton {
public:
    static T& instance() {
        static T instance;
        return instance;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

protected:
    Singleton() {}
};

使用时:

class Foo : public Singleton <Foo> {
    friend class Singleton <Foo>;
    Foo() {}
public:
    void doSomething() {}
};

优点

  • 可以为不同类复用同一单例实现。
  • 代码仍然保持简洁。

小结

  • 最简单:局部静态变量(懒汉式)— 适用于大多数情况,C++11 及以后已保证线程安全。
  • 需要手动销毁std::call_once + new/deleteunique_ptr/shared_ptr
  • 性能优化:双重检查锁(需小心实现)。
  • 复用:模板化单例。

在实际项目中,首选局部静态变量实现;如果需要在特定时间销毁实例或需要跨平台支持更老的编译器,考虑使用 std::call_once 或双重检查锁。

发表评论