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

在多线程环境下,单例模式的实现需要保证只有一个实例被创建,并且在并发访问时不会出现竞争条件。以下将介绍几种常见的线程安全单例实现方式,并给出完整代码示例。

1. 局部静态变量(C++11 之后的线程安全初始化)

C++11 标准保证了函数内部局部静态变量的初始化是线程安全的。最简单的实现方式就是利用这一特性。

class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance;   // 线程安全的局部静态
        return instance;
    }

    // 删除拷贝构造和赋值操作,防止复制
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() {}   // 私有构造
};

优点:

  • 代码简洁,易于维护。
  • 只在第一次调用 getInstance() 时创建实例,后续调用不产生额外开销。

缺点:

  • 在某些极端情况下,如果初始化时抛出异常,后续调用仍会重新尝试初始化,这可能导致异常重现。

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

双重检查锁是一种懒汉式的实现,先不加锁检查实例是否存在,只有第一次发现实例为空时才加锁,随后再次检查后才创建实例。

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

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

private:
    Singleton() {}
    ~Singleton() {}

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

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

需要注意:

  • 这里使用 std::atomic 保证指针的可见性。
  • 由于 new Singleton() 可能发生异常,后续再次获取实例时可能出现重复创建的情况。为此可以使用 std::unique_ptrstd::shared_ptr 包装。

3. Meyer’s Singleton(局部静态加 std::call_once

结合 std::call_once 可以更显式地控制初始化逻辑,并确保仅执行一次。

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

private:
    Singleton() {}
    static std::unique_ptr <Singleton> instance;
    static std::once_flag initFlag;
};

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

这种方式的好处是:

  • 初始化过程可写成闭包,逻辑更灵活。
  • std::call_once 的内部实现一样,保证线程安全且只执行一次。

4. 静态成员指针 + 静态局部函数

如果想在单例类之外进行实例化管理,可以使用静态成员指针和静态局部函数来实现。

class Singleton {
public:
    static Singleton* instance() {
        static Singleton* ptr = init();
        return ptr;
    }

private:
    static Singleton* init() {
        return new Singleton();
    }

    Singleton() {}
};

这里的 static Singleton* ptr 通过 init() 初始化,保证了线程安全。

5. 结合 std::shared_ptr 的线程安全单例

如果单例需要在多个线程中共享,并且希望自动销毁,使用 std::shared_ptr 也是可行的。

class Singleton {
public:
    static std::shared_ptr <Singleton> getInstance() {
        static std::shared_ptr <Singleton> ptr(new Singleton);
        return ptr;
    }

private:
    Singleton() {}
};

注意:此方法在 C++11 之后同样保证了初始化线程安全。

小结

  • 推荐:C++11 之后的局部静态变量实现(Meyer’s Singleton)是最简洁且可靠的方案。
  • 特殊需求:若需要自定义销毁时机或延迟初始化,std::call_once 或双重检查锁也可考虑。
  • 性能考虑:局部静态变量在第一次调用时才创建,后续访问成本极低,适合大多数场景。

通过上述方法,你可以在多线程环境下安全地实现单例模式,避免了传统实现中的竞态条件和性能问题。

发表评论