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

单例模式(Singleton Pattern)是一种常见的软件设计模式,确保一个类只有一个实例,并提供全局访问点。在多线程环境下实现线程安全的单例会面临竞争条件和初始化顺序的问题。下面介绍几种常见的线程安全实现方式,并给出完整可编译的示例代码。


1. Meyer’s 单例(局部静态变量)

C++11 起,函数内部局部静态变量的初始化是线程安全的。最简单、最推荐的方式是使用这种技术。

#include <iostream>
#include <mutex>

class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance;   // 线程安全初始化
        return instance;
    }

    void sayHello() {
        std::cout << "Hello from Singleton! Thread ID: " << std::this_thread::get_id() << '\n';
    }

private:
    Singleton()  { std::cout << "Singleton constructed\n"; }
    ~Singleton() {}
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

说明

  • 局部静态对象:在第一次调用 getInstance() 时才会创建,后续调用直接返回已创建的实例。
  • C++11 规定:局部静态变量的初始化是原子且线程安全的,编译器会生成相应的锁。
  • 优点:代码最简洁,天然支持延迟初始化,且无显式锁开销。
  • 缺点:在旧的 C++11 编译器实现中可能有微小的性能开销;如果实例的构造抛异常,后续调用会再次尝试初始化。

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

如果你想在更早的标准(如 C++03)中实现线程安全,可以使用双重检查锁。然而需要小心内存可见性和编译器重排序问题。C++11 的 std::atomicstd::once_flag 可以帮助实现更安全的版本。

#include <atomic>
#include <mutex>

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

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

    static std::atomic<Singleton*> instance;
    static std::mutex mtx;
};

std::atomic<Singleton*> Singleton::instance{nullptr};
std::mutex Singleton::mtx;

说明

  • std::atomic 保证了指针写入后对所有线程可见。
  • std::memory_order_acquire/release 规范了内存可见性。
  • 仍有一次锁的开销(但只在第一次调用时)。

3. std::call_oncestd::once_flag

std::call_once 是标准库为一次性初始化提供的最安全、最便捷方式。它内部使用了高效的原子操作和一次性锁,适合 C++11 及以上。

#include <iostream>
#include <mutex>

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

private:
    Singleton() { std::cout << "Singleton constructed\n"; }
    ~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::once_flag 只被初始化一次,且是线程安全的。
  • std::call_once 的实现通常比手写锁更高效。
  • 适用于需要显式控制实例生命周期的场景(例如单例需要在程序结束前析构)。

4. 现代化的懒汉式单例(使用 std::shared_ptr

如果你希望单例支持自动释放,或者需要在多线程环境中使用 shared_ptr,可以结合 std::shared_ptrstd::call_once

#include <memory>
#include <mutex>

class Singleton {
public:
    static std::shared_ptr <Singleton> getInstance() {
        std::call_once(initFlag, [](){
            instance = std::make_shared <Singleton>();
        });
        return instance;
    }

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

    static std::shared_ptr <Singleton> instance;
    static std::once_flag initFlag;
};

std::shared_ptr <Singleton> Singleton::instance = nullptr;
std::once_flag Singleton::initFlag;

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

在一些嵌入式或资源受限的环境下,单例不一定要在程序开始时就创建。以下是一种基于 std::unique_ptr 的懒加载与手动销毁实现:

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

    static void destroyInstance() {
        std::lock_guard<std::mutex> lock(mtx);
        delete instance;
        instance = nullptr;
    }

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

    static Singleton* instance;
    static std::mutex mtx;
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

注意:手动销毁时务必保证没有其他线程仍在使用实例,否则会导致悬空引用。


6. 何时使用哪种实现?

场景 推荐实现
C++11 及以上,且不需要自定义销毁 Meyer’s 单例(局部静态)
需要显式销毁,或 C++11 以上 std::call_once + std::shared_ptrunique_ptr
旧编译器(C++03) 双重检查锁 + std::atomic
需要更细粒度的锁控制 手写双重检查锁

7. 小结

  • 单例模式在多线程环境中实现时,关键是 初始化顺序内存可见性
  • C++11 之后,使用局部静态变量或 std::call_once 是最安全、最简洁的做法。
  • 若使用旧标准,需借助 std::atomic + 双重检查锁,或使用第三方库如 Boost 的 boost::singleton.
  • 在性能敏感的场景下,Meyer’s 单例几乎没有锁开销,适合作为首选。

希望这份指南能帮助你在项目中稳健地实现线程安全的单例模式。祝编码愉快!

发表评论