单例设计模式数据共享

七、单例设计模式

1、单例类的设计

在众多的设计模式中,单例设计模式使用的频率比较高,这里做一个总结。所谓单例:是整个项目中,有某个或者某些特殊的类,只能创建一个该类的对象。这个类可以通过特殊的写法构建:

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
#include "pch.h"                   // vs需要包含
#include <iostream>
class MyCAS // 这是一个单例类
{
private:
MyCAS() {} // 私有的构造函数,这样不能通过MyCAS a;实例化出对象
private:
static MyCAS* my_instance; // 静态变量由所有对象共享!一定得是static
public:
static MyCAS* GetInstance()
{
if (NULL == my_instance)
{
my_instance = new MyCAS(); // 多线程下,这里不安全(多个线程同时创建类对象)
static GarbageCollection garcolle; // delete
}
return my_instance;
}

void Test()
{
std::cout << "Hello MyCAS " <<this<< std::endl;
}

class GarbageCollection // garbage collection,设计的很巧妙
{
public:
~GarbageCollection()
{
if (MyCAS::my_instance)
{
delete MyCAS::my_instance;
MyCAS::my_instance = NULL;
}
}

};
};
// 静态成员变量在类外边定义
MyCAS* MyCAS::my_instance = NULL;
int main()
{
MyCAS* mycas_ptr = MyCAS::GetInstance();
MyCAS* mycas_ptr2 = MyCAS::GetInstance(); // 实际上是同一个东西
mycas_ptr->Test();
return 0;
}

这里对new出来的对象进行回收使用到了一个小技巧,构建一个静态的GarbageCollection对象,它的生命周期是整个程序空间,当程序全部执行完成,会析构GarbageCollection对象,与此同时delete掉了new出的对象。

2、单例设计模式共享数据

多线程访问只读的共享数据不需要加锁进行特别的处理,这样只要在主线程中将单例类型GetInstance(),并且装载对应的数据,其他线程就可以安全的只读这个类。但是我们可能面临着在我们自己创建的线程中创建MyCAS这个单例类的对象(多个线程),这时我们会面临GetInstance()这种成员函数需要互斥。

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
#include "pch.h"
#include <iostream>
#include <thread>
#include <mutex>
class MyCAS
{
private:
MyCAS() {}
private:
static MyCAS* my_instance;
static std::mutex my_mutex; // 添加的互斥锁
public:
static MyCAS* GetInstance()
{
if (NULL == my_instance) // 一个trick
{ // 双重否定,这里只有创建单例对象时,才存在多个线程同时new的问题
// 去掉这个if,每次线程需要访问单例对象,通过GetInstance()获得指针
// 都会阻塞其他线程,效率低下;这个双重否定解决了这个问题
std::unique_lock<std::mutex> mutex_guard(my_mutex);
if (NULL == my_instance)
{
my_instance = new MyCAS();
static GarbageCollection garcolle;
}
return my_instance;
}
}

void Test()
{
std::cout << "Hello MyCAS " << this<<std::endl;
}

class GarbageCollection // garbage collection
{
public:
~GarbageCollection()
{
if (MyCAS::my_instance)
{
delete MyCAS::my_instance;
MyCAS::my_instance = NULL;
}
}

};
};

// 静态成员变量在类外边定义
MyCAS* MyCAS::my_instance = NULL;
std::mutex MyCAS::my_mutex;

// 线程入口函数
void mythread()
{
std::cout << "我的线程 " << std::this_thread::get_id() <<" 创建。"<< std::endl;
MyCAS* p_a = MyCAS::GetInstance(); // 这里不会出现问题
std::cout << "我的线程 " << std::this_thread::get_id() <<" 执行完毕"<< std::endl;
}
int main()
{
std::thread myobj1(mythread);
std::thread myobj2(mythread);
myobj1.join();
myobj2.join();
return 0;
}

3、std::call_once

std::call_once是C++11引入的函数,该函数需要与一个标记结合使用,这个标记是std::once_flag;该函数的第二个参数是一个函数名a()call_once的功能是保证函数a()只被调用一次。call_once就是通过这个标记来决定对应的函数a()是否执行,调用call_once成功后,call_once就把这个标记设置为一种已调用的状态,后续再次调用call_once,只要once_flag被设置为了”已调用”状态,那么对应的函数a()就不会再被执行了。

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
class MyCAS                      
{
private:
MyCAS() {}
private:
static MyCAS* my_instance;
static std::mutex my_mutex;
static std::once_flag g_flag; // call_once的第一参数

static void CreateInstance() { // 需要执行一次的代码提取出来(需要是静态的函数)
my_instance = new MyCAS(); //非静态成员(变量和方法)属于类的对象,只有在类的对象产生(创建类的实例)时才会分配内存
static GarbageCollection garcolle;
}
public:
static MyCAS* GetInstance()
{
std::call_once(g_flag, CreateInstance); // 假设两个线程同时执行到这,只有第一个线程可以执行CreateInstance;
return my_instance; // 另外一个线程阻塞,等到另一个线程执行完毕,第二个线程在根据g_flag决定是否执行
}
// ... 还是在上一个例子中的代码,不粘贴了
};
// 静态成员变量在类外边定义
MyCAS* MyCAS::my_instance = NULL;
std::mutex MyCAS::my_mutex;
std::once_flag MyCAS::g_flag;
// ...
听说打赏我的人,最后都找到了真爱。