单例类:
(1) 单例类保证全局只有一个唯一的实例对象。
(2) 单例类保证只有唯一的接口获取这唯一实例。
非线程安全的单例类举例:
1 class CSingleton 2 { 3 public: 4 ~CSingleton(){} 5 static CSingleton * getInstance() 6 { 7 if (m_instance == nullptr) 8 { 9 m_instance = new CSingleton; 10 } 11 return m_instance; 12 } 13 static void delInstance() 14 { 15 if (m_instance) 16 { 17 delete m_instance; 18 m_instance = nullptr; 19 } 20 } 21 void print() 22 { 23 std::cout << "print test" << std::endl; 24 } 25 private: 26 CSingleton(){} 27 CSingleton & operator=(const CSingleton & ) = delete; 28 CSingleton(const CSingleton &) = delete; 29 private: 30 static CSingleton * m_instance; 31 }; 32 33 CSingleton * CSingleton::m_instance = nullptr;
上述单例类面对多线程并发访问时会出错。
看如下线程安全的单例类(非C++11实现)
1 class CSingleton
2 {
3 public:
4 ~CSingleton() {}
5 static CSingleton * getInstance()
6 {
7 if (m_instance == nullptr)
8 {
9 std::lock_guard<std::mutex> lgd(m_mt);
10 if (m_instance == nullptr)
11 {
12 m_instance = new CSingleton;
13 }
14 }
15 return m_instance;
16 }
17 static void delInstance()
18 {
19 std::lock_guard<std::mutex> lgd(m_mt);
20 if (m_instance)
21 {
22 delete m_instance;
23 m_instance = nullptr;
24 }
25 }
26 void print()
27 {
28 std::cout << "print test" << std::endl;
29 }
30 private:
31 CSingleton() {}
32 CSingleton & operator=(const CSingleton & ) = delete;
33 CSingleton(const CSingleton &) = delete;
34 private:
35 static CSingleton * m_instance;
36 static std::mutex m_mt;
37 };
38
39 CSingleton * CSingleton::m_instance = nullptr;
40 std::mutex CSingleton::m_mt;
当然绝对的线程安全还是有问题,因为C++创建对象时,会执行1、分配内存,2 调用构造,3 赋值操作三步操作,然而现代CPU和编译器高并发下可能
会进行乱序重排操作,因而创建对象new CSingleton的第2步可能会晚于第3步进行指令调用,因而导致出现未定义的的行为。
举例:
线程A : getInstance 判断 instance是否为空,为空则
线程A : 分配内存 此时CPU乱序指令重排,赋值操作提前
线程B : getInsnace 判断instance是否为空,非空,则返回
线程B : 使用了未初始化的instacne 出现未定义行为。
线程A : 调用构造函数对instance初始化。
因此要解决上述问题需要引入内存栅栏来确保指令运行的同步性。在CPU指令重排的前提下保持数据的一致性。
C++11支持线程安全的单例类:
C++11的单例模式的实现
1 class CSingleton
2 {
3 public:
4 ~CSingleton() {}
5 static CSingleton & getInstance()
6 {
7 static CSingleton m_instance;
8 return m_instance;
9 }
10 void print()
11 {
12 std::cout << "print test" << std::endl;
13 }
14 };
返回静态局部对象的引用,C++11中是线程安全的。
验证一下:
1 class CStatic
2 {
3 public:
4 CStatic()
5 {
6 std::cout << "construct begin" << std::endl;
7 Sleep(5000);
8 std::cout << "construct end" << std::endl;
9 }
10 void print()
11 {
12 std::cout << "print" << std::endl;
13 std::cout << s_num++ << std::endl;
14 }
15 static int s_num;
16 static std::mutex s_mt;
17 };
1 int CStatic::s_num = 0;
2 std::mutex CStatic::s_mt;
3
4 //
5 void thread_func()
6 {
7 static CStatic st;
8 st.print();
9 }
10
11 int main()
12 {
13 std::vector<std::thread> vecThread;
14 for (auto i = 0; i< 8; i++)
15 {
16 vecThread.push_back(std::thread(thread_func));
17 }
18 for (auto i = 0; i< 8; i++)
19 {
20 vecThread[i].join();
21 }
22 //
23 system("pause");
24 return 0;
25 }
首先我们创建一个CStatic类,然后创建8个线程来启动thread_func(),thread_func()初始化了一个静态CStatic对象,(静态局部变量仅被初始化一次)
然后接着运行。我们发现,当首个线程初始化CStatic时,其他线程都是被阻塞的,从构造函数的begin和end中可以看到,我们故意让其停留5s,
如下图,其他线程都是在st被初始化之后才运行。
所以CStatic静态局部对象被构造的过程中是线程安全的,但是其拥有的成员变量则不是线程安全的。
因此我们增加个简单的锁,
1 class CStatic
2 {
3 public:
4 CStatic()
5 {
6 std::cout << "construct begin" << std::endl;
7 Sleep(5000);
8 std::cout << "construct end" << std::endl;
9 }
10 void print()
11 {
12 std::lock_guard<std::mutex> lgd(s_mt);
13 std::cout << "print" << std::endl;
14 std::cout << s_num++ << std::endl;
15 }
16 static int s_num;
17 static std::mutex s_mt;
18 };