本文转载小林coding的文章,可由文末参考章节进入原文阅读
线程安全
什么是线程安全?
在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确地执行,不会出现数据污染等意外情况。
如何保证线程安全?
- 给共享的资源加锁,保证每个资源变量每时每刻至多被一个线程占用。
- 让线程也拥有资源,不用去共享进程中的资源。如:threadlocal为每个线程维护一个私有的本地变量。
单例模式简介
什么是单例模式?
单例模式是指在整个系统生命周期里,保证一个类只能产生一个实例,确保该类的唯一性。
单例模式的分类
单例模式可以分为懒汉式和饿汉式,两者之间的区别在于创建实例的时间不同:
- 懒汉式:指系统运行中,实例并不存在,只有当需要使用该实例时,才会去创建并使用实例。(这种方式需要考虑线程安全)
- 饿汉式:指系统一运行,就初始化创建实例,当需要时,直接调用即可。(本身就线程安全,没有多线程的问题)
单例模式的特点
- 构造函数和析构函数为private类型,目的是禁止外部构造和析构
- 拷贝构造和赋值构造函数为private类型,目的是禁止外部拷贝和复制,确保实例的唯一性
- 类里有个获取实例的静态函数,可以全局访问
单例模式详解
普通懒汉式单例(线程不安全)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
| #include <iostream> #include <mutex> #include <pthread.h>
class SingleInstance {
public: static SingleInstance *GetInstance();
static void deleteInstance(); void Print();
private: SingleInstance(); ~SingleInstance();
SingleInstance(const SingleInstance &signal); const SingleInstance &operator=(const SingleInstance &signal);
private: static SingleInstance *m_SingleInstance; };
SingleInstance *SingleInstance::m_SingleInstance = NULL;
SingleInstance* SingleInstance::GetInstance() {
if (m_SingleInstance == NULL) { m_SingleInstance = new (std::nothrow) SingleInstance; }
return m_SingleInstance; }
void SingleInstance::deleteInstance() { if (m_SingleInstance) { delete m_SingleInstance; m_SingleInstance = NULL; } }
void SingleInstance::Print() { std::cout << "我的实例内存地址是:" << this << std::endl; }
SingleInstance::SingleInstance() { std::cout << "构造函数" << std::endl; }
SingleInstance::~SingleInstance() { std::cout << "析构函数" << std::endl; }
void *PrintHello(void *threadid) { pthread_detach(pthread_self());
int tid = *((int *)threadid);
std::cout << "Hi, 我是线程 ID:[" << tid << "]" << std::endl;
SingleInstance::GetInstance()->Print();
pthread_exit(NULL); }
#define NUM_THREADS 5
int main(void) { pthread_t threads[NUM_THREADS] = {0}; int indexes[NUM_THREADS] = {0};
int ret = 0; int i = 0;
std::cout << "main() : 开始 ... " << std::endl;
for (i = 0; i < NUM_THREADS; i++) { std::cout << "main() : 创建线程:[" << i << "]" << std::endl; indexes[i] = i; ret = pthread_create(&threads[i], NULL, PrintHello, (void *)&(indexes[i])); if (ret) { std::cout << "Error:无法创建线程," << ret << std::endl; exit(-1); } }
SingleInstance::deleteInstance(); std::cout << "main() : 结束! " << std::endl; return 0; }
|
运行结果如下。从运行结果可以看出,单例构造函数创建了两个,内存地址分别为0x7f3c980008c0和0x7f3c900008c0。所以普通懒汉式单例只适合单进程不适合多进程,因为线程是不安全的。

加锁的懒汉式单例(线程安全)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| class SingleInstance {
public: static SingleInstance *&GetInstance();
static void deleteInstance(); void Print();
private: SingleInstance(); ~SingleInstance();
SingleInstance(const SingleInstance &signal); const SingleInstance &operator=(const SingleInstance &signal);
private: static SingleInstance *m_SingleInstance; static std::mutex m_Mutex; };
SingleInstance *SingleInstance::m_SingleInstance = NULL; std::mutex SingleInstance::m_Mutex;
SingleInstance *&SingleInstance::GetInstance() {
if (m_SingleInstance == NULL) { std::unique_lock<std::mutex> lock(m_Mutex); if (m_SingleInstance == NULL) { m_SingleInstance = new (std::nothrow) SingleInstance; } }
return m_SingleInstance; }
void SingleInstance::deleteInstance() { std::unique_lock<std::mutex> lock(m_Mutex); if (m_SingleInstance) { delete m_SingleInstance; m_SingleInstance = NULL; } }
void SingleInstance::Print() { std::cout << "我的实例内存地址是:" << this << std::endl; }
SingleInstance::SingleInstance() { std::cout << "构造函数" << std::endl; }
SingleInstance::~SingleInstance() { std::cout << "析构函数" << std::endl; }
|
运行结果如下图。从运行结果可以看出,程序只创建了一个实例,内存地址是0x7f28b00008c0,所以加了互斥锁的普通懒汉式是线程安全的。

内部静态变量的懒汉单例(C++11线程安全)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| class Single {
public: static Single &GetInstance(); void Print();
private: Single();
~Single();
Single(const Single &signal);
const Single &operator=(const Single &signal); };
Single &Single::GetInstance() { static Single signal; return signal; }
void Single::Print() { std::cout << "我的实例内存地址是:" << this << std::endl; }
Single::Single() { std::cout << "构造函数" << std::endl; }
Single::~Single() { std::cout << "析构函数" << std::endl; }
|
运行结果如下图。从运行结果可以看到,内部静态变量的懒汉单例只创建了一次实例,程序代码量小,推荐使用该方法。

饿汉式单例(本身就线程安全)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| class Singleton { public: static Singleton* GetInstance();
static void deleteInstance(); void Print();
private: Singleton(); ~Singleton();
Singleton(const Singleton &signal); const Singleton &operator=(const Singleton &signal);
private: static Singleton *g_pSingleton; };
Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton;
Singleton* Singleton::GetInstance() { return g_pSingleton; }
void Singleton::deleteInstance() { if (g_pSingleton) { delete g_pSingleton; g_pSingleton = NULL; } }
void Singleton::Print() { std::cout << "我的实例内存地址是:" << this << std::endl; }
Singleton::Singleton() { std::cout << "构造函数" << std::endl; }
Singleton::~Singleton() { std::cout << "析构函数" << std::endl; }
|
运行结果如下图。从运行结果可见,饿汉式程序在一开始就进行了初始化,所以本身就是线程安全的。

特点与选择
- 懒汉式是以时间换空间,适用于访问量较小的情况;推荐使用内部静态变量的懒汉单例,代码量小
- 饿汉式是以空间换时间,适用于访问量较大的情况,或者线程比较多的情况
参考
C++线程安全的单例模式总结