使用单例模式进行多线程编程
[[单例模式]]简而言之就是程序中的某个类只能实例化一个对象。因为对象只有一个,在不同线程中实例化的时候,实际上都是获得那个唯一对象的引用或者指针,那么如果通过这个唯一的对象去共享数据,就可以实现多线程中的数据共享。
单例模式类的设计
首先看一下 [[Cpp]] 中如何实现单例模式的类的设计
class MyCAS {
private:
MyCAS() {} // 构造函数私有,禁止通过普通的方式实例化
private:
static MyCAS* m_instance;
public:
static MyCAS* Getinstance() {
if (m_instance == NULL) {
m_instance = new MyCAS();
static CGarhuishou cl; // 生命周期一直到程序退出
}
return m_instance;
}
class CGarhuishou { // 用于管理单例对象资源的类
public:
~CGarhuishou() {
if (MyCAS::m_instance) {
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
void func() { cout << "测试" << endl; } // 其他成员函数
};
// 在源码文件中对静态成员变量进行定义和初始化
MyCAS* MyCAS::m_instance=NULL; // 类的静态成员变量定义和初始化
注意到该类的构造函数是用 private
修饰的,这样就不能创建基于该类的对象了。例如下面的代码都将无法编译通过,这正是单例类要达到的效果。
MyCAS a1; // 非法
MyCAS *pa = new MyCAS(); // 非法
只能通过以下方式实例化对象:
MyCAS *p_a = MyCAS::Getinstance();
这里就创建了一个 MyCAS
对象的指针。
另外,由 CGarhuishou
类来管理 m_instance
指针所指向资源的回收。
单例模式应用并发编程
首先建议单例模式的类在所有子线程启动前实例化,这样可以避免一些多线程访问对象资源冲突的问题。
如果我们不这样做,可能会发生什么?想象一下,子线程 1 和子线程 2 中同时进行实例化,当子线程 1 在执行了
if (m_instance == NULL)
时,程序切换到了子线程 2,此时子线程 2 也执行到了
if (m_instance == NULL)
这样,两个线程都会认为 m_instance == NULL
是 true
,那么还是会执行两次
m_instance = new MyCAS();
为了解决这个问题,可以使用互斥锁+双重检查的方式。
static MyCAS* Getinstance() {
if (m_instance == NULL) {
std::unique_lock<std::mutex> mymutex(resource_mutex);
if (m_instance == NULL) {
m_instance = new MyCAS();
static CGarhuishou cl; // 生命周期一直到程序退出
}
}
return m_instance;
}
因为在多线程中 m_instance == NULL
成立不一定代表唯一执行 m_instance = new MyCAS();
,很有可能切换线程了。但是使用互斥锁就能在几个线程同时 new
的时候只有一个继续往下实例化,而当实例化完后释放互斥锁,其他线程再一次检查 m_instance == NULL
时就已经是 FALSE
了。
另一种写法
使用 std::call_once
保证函数只被调用一次。假设有个函数,名字为 a
,call_once
的功能就是能够保证函数 a
只被调用一次。读者都知道,例如有两个线程都调用函数 a
,那么这个函数 a
肯定是会被调用两次。但是,有了 call_once
,就能保证,即便是在多线程下,这个函数 a
也只会被调用一次。
代码实现:
std::once_flag g_flag; //这是一个系统定义的标记
实际上 call_once
就是控制这个 flag
来实现,函数只能被单次调用。
另外在 MyCAS
里面再新增一个函数
private:
static void CreateInstance(){
m_instance = new MyCAS();
static CGarhuishou cl;
}
并修改 Getinstance()
函数
static MyCAS* Getinstance() {
if (m_instance == NULL) {
std::call_once(g_flag, CreateInstance);
}
return m_instance;
}
这样,即使是两个线程都判断 m_instance == NULL
成立并进入内部作用域后,CreateInstance
仍然只会执行一次,确保只会实例化一个对象。
Reference
[[C++新经典]]
时间仓促,如有错误欢迎指出,欢迎在评论区讨论,如对您有帮助还请点个推荐、关注支持一下
作者: pomolnc
出处: https://www.cnblogs.com/pomolnc/p/17774826.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文链接,否则保留追究法律责任的权利。
若内容有侵犯您权益的地方,请公告栏处联系本人,本人定积极配合处理解决。