**题目:如何在C++中实现多线程的线程安全单例模式?**

在C++开发中,单例模式常被用于需要共享全局状态或资源的场景,如日志系统、配置管理、数据库连接池等。然而,当程序在多线程环境下启动时,若单例实现不当,可能导致竞态条件、重复实例化甚至崩溃。本文将以C++17为基础,详细阐述几种线程安全单例实现方法,并给出完整代码示例,帮助你在多线程环境下安全使用单例。


1. 传统懒汉式单例(非线程安全)

class LazySingleton {
public:
    static LazySingleton& instance() {
        if (!m_instance) {
            m_instance.reset(new LazySingleton);
        }
        return *m_instance;
    }

private:
    LazySingleton() = default;
    LazySingleton(const LazySingleton&) = delete;
    LazySingleton& operator=(const LazySingleton&) = delete;

    static std::unique_ptr <LazySingleton> m_instance;
};

std::unique_ptr <LazySingleton> LazySingleton::m_instance = nullptr;

上述实现采用懒汉式:第一次访问 instance() 时才创建对象。缺点:在多线程同时调用 instance() 时,多个线程可能进入 if (!m_instance) 判断,导致多次实例化,破坏单例性质。


2. 线程安全的懒汉式单例

2.1 带锁实现

#include <mutex>

class ThreadSafeLazySingleton {
public:
    static ThreadSafeLazySingleton& instance() {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (!m_instance) {
            m_instance.reset(new ThreadSafeLazySingleton);
        }
        return *m_instance;
    }

private:
    ThreadSafeLazySingleton() = default;
    ThreadSafeLazySingleton(const ThreadSafeLazySingleton&) = delete;
    ThreadSafeLazySingleton& operator=(const ThreadSafeLazySingleton&) = delete;

    static std::unique_ptr <ThreadSafeLazySingleton> m_instance;
    static std::mutex m_mutex;
};

std::unique_ptr <ThreadSafeLazySingleton> ThreadSafeLazySingleton::m_instance = nullptr;
std::mutex ThreadSafeLazySingleton::m_mutex;
  • 优点:使用 std::lock_guard 保证同一时间只有一个线程能进入初始化块,避免竞态。
  • 缺点:每次获取实例都会加锁,性能略受影响。

2.2 双重检查锁(Double-Checked Locking)

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

private:
    DCLSingleton() = default;
    DCLSingleton(const DCLSingleton&) = delete;
    DCLSingleton& operator=(const DCLSingleton&) = delete;

    static std::atomic<DCLSingleton*> m_instance;
    static std::mutex m_mutex;
};

std::atomic<DCLSingleton*> DCLSingleton::m_instance{nullptr};
std::mutex DCLSingleton::m_mutex;
  • 原理:首次访问无锁检查实例是否已创建,若未创建再加锁并再次检查,避免多次创建。
  • 注意:需使用 std::atomic 并正确的内存序保证可见性。

3. 静态局部变量实现(C++11+)

class StaticLocalSingleton {
public:
    static StaticLocalSingleton& instance() {
        static StaticLocalSingleton instance; // C++11 起保证线程安全
        return instance;
    }

private:
    StaticLocalSingleton() = default;
    StaticLocalSingleton(const StaticLocalSingleton&) = delete;
    StaticLocalSingleton& operator=(const StaticLocalSingleton&) = delete;
};
  • 优势:语义简洁,编译器保证线程安全,性能高。
  • 实现细节:C++11 通过“调用时初始化”实现 static 变量的线程安全,内部使用了 std::call_once 机制。

4. 线程安全的 Meyer’s 单例(又称“饿汉式”)

class MeyersSingleton {
public:
    static MeyersSingleton& instance() {
        return *m_instance;
    }

private:
    MeyersSingleton() = default;
    MeyersSingleton(const MeyersSingleton&) = delete;
    MeyersSingleton& operator=(const MeyersSingleton&) = delete;

    static std::unique_ptr <MeyersSingleton> m_instance;
};

std::unique_ptr <MeyersSingleton> MeyersSingleton::m_instance = std::make_unique<MeyersSingleton>();
  • 原理:在程序启动时就构造实例,适合不需要懒加载的场景。
  • 线程安全:构造时机已确定,且无并发访问风险。

5. 对比与选择

实现方式 线程安全 延迟加载 性能 代码复杂度
带锁懒汉式 中等
DCL 中等
静态局部变量
Meyer’s 单例
饿汉式
  • 最佳实践:若不需要延迟加载,推荐使用 静态局部变量Meyers 单例。其实现最简洁且性能最优。
  • 若业务需要在第一次使用时才创建对象,静态局部变量 仍是最优选择,且无需手动锁。
  • 传统的 std::unique_ptr + std::mutex 方案仅在兼容旧编译器或特殊需求时使用。

6. 小结

多线程环境下实现线程安全的单例并不复杂,关键在于正确使用 C++11 及之后版本提供的同步机制。最常用且推荐的做法是利用 static 局部变量,它既简洁又安全。若你对性能有极致要求或想了解更底层实现,可进一步学习 std::call_oncestd::atomic 或双重检查锁。

下面给出一个完整的示例,演示在多线程环境下安全使用 StaticLocalSingleton

#include <iostream>
#include <thread>
#include <vector>

class StaticLocalSingleton {
public:
    static StaticLocalSingleton& instance() {
        static StaticLocalSingleton inst;
        return inst;
    }
    void do_something() { std::cout << "Thread " << std::this_thread::get_id() << " using singleton\n"; }

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

void worker() {
    StaticLocalSingleton::instance().do_something();
}

int main() {
    std::vector<std::thread> ths;
    for (int i = 0; i < 10; ++i)
        ths.emplace_back(worker);
    for (auto& t : ths) t.join();
    return 0;
}

运行结果:

Singleton constructed
Thread 140122152562176 using singleton
Thread 140122143169472 using singleton
...

可以看到,只会打印一次“Singleton constructed”,说明单例实例只创建一次,且在所有线程间安全共享。

祝你编码愉快!

发表评论