Linux:线程安全的单例模式
设计模式
设计模式听上去是个很高贵的名词,其实就是是一套 多数人知晓、被反复使用、经过分类编目的、代码设计经验的总结,简称:对于编程比较典的场景的解决方案
单例模式
单例模式就是其中一种设计模式,是设计模式里的创建型模式(设计模式包含很多种)
单例模式:确保一个类只有一个实例,提供一个全局访问点来访问这个实例,并提供一个全局访问点来获取这个实例。
单例模式通常用于游戏额需要频繁创建和销毁同一对象的场景,单例模式可以减少系统性能开销。
举个例子:在一家火锅店,客人需要火锅调料可以是各种各样的,而商家不会设置很多个自助调料区分开放在不同的地方,而会把他们放在一起,这个所有调料集中的区域就是唯一的自助调料区
在这个栗子中:我们确保了只有一个自助调料区这个实例,提供唯一的位置(全局访问点)来访问这个实例,大家都可以随时、同时、同地访问,避免了资源的浪费
单例模式有两种分类:懒汉模式、饿汉模式
懒汉模式、饿汉模式
等你搞懂就被淘汰啦!
看看下面的代码:
#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int value;
}Singleton;
/*
Singleton*getInstance(){
static Singleton instance;
return &instance;//直接返回,饿汉模式
}
*/
Singleton*getInstance(){
static Singleton* instance=NULL;
if(instance==NULL){//用到的时候才初始化,很懒
instance=(Singleton*)malloc(sizeof(Singleton));
instance->value=0;
}
return instance;
}
int main(){
Singleton* p1=getInstance();
Singleton* p2=getInstance();
printf("p1==%p\n",p1);//p1==0x562d3adac2a0
//p2==0x562d3adac2a0
printf("p2==%p\n",p2);//打印发现地址一样
return 0;
}
#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int value;
}Singleton;
/*
Singleton*getInstance(){
static Singleton* instance=NULL;
if(instance==NULL){//用到的时候才初始化,很懒
instance=(Singleton*)malloc(sizeof(Singleton));
instance->value=0;
}
return instance;
}
*/
Singleton*getInstance(){
static Singleton instance;
return &instance;//直接返回,饿汉模式
}
int main(){
Singleton* p1=getInstance();
Singleton* p2=getInstance();
printf("p1==%p\n",p1);//p1==0x563ce8622014
//p2==0x563ce8622014
printf("p2==%p\n",p2);//打印发现地址一样
return 0;
}
首先在打印的时候我们可以看出p1和p2的地址是一样的,说明他们是同一个实例,符合单例模式
其次我们来看两者的区别:
饿汉模式是很饥饿,程序启动时就实例化;懒汉模式如其名,只有需要的时候才会创建实例
吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭
吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式
关于线程安全
饿汉模式因为每次都只涉及到读操作,所以不会引发线程安全问题;但是懒汉模式因为涉及到了读写操作,就为线程安全问题的产生埋下了隐患。
由于线程调度的随机性,当两个线程在同一时间调用该方法时,错落的执行顺序可能导致if语句出现不可避免的错判,进而导致最终创建了两个SingletonLazy实例。
偷的图(java写的),大概就是这个意思
①T1线程执行完if语句,因为第一次调用getIntance方法,intance==null,所以T1线程接下来将要创建SingletonLazy实例,并将其赋值给intance。
②轮到T2线程执行,由于T1线程中尚未进行实例创建,此时仍旧是instance==null,所以if语句判断通过。接下来创建实例、赋值一气呵成,最后还将创建的Singleton对象返回。
③再次轮到T1线程,继续执行,创建了一个和T2线程不同的Singleton实例,引用赋给instance。最后,这个引用又被返回。
综上所述,在多线程情况下竟然出现了两个懒汉实例,这不符合单例模式下一个类只能创建一个实例的原则,很可能产生无法预估的错误,妥妥的bug代码。所以单线程下实现的懒汉模式不是线程安全的。
我们可以引入锁:
#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int value;
}Singleton;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
Singleton*getInstance(){
static Singleton* instance=NULL;
if(instance==NULL){//用到的时候才初始化,很懒
pthread_mutex_lock(&mutex);
if(instance==NULL){//再判断一次,因为多线程情况下可能由于锁竞争陷入阻塞,所以其他线程可能创建过实例了
instance=(Singleton*)malloc(sizeof(Singleton));
instance->value=0;
pthread_mutex_unlock(&mutex);
}
}
return instance;
}
/*
Singleton*getInstance(){
static Singleton instance;
return &instance;//直接返回,饿汉模式
}
*/
int main(){
Singleton* p1=getInstance();
Singleton* p2=getInstance();
printf("p1==%p\n",p1);//p1==0x562d3adac2a0
//p2==0x562d3adac2a0
printf("p2==%p\n",p2);//打印发现地址一样
return 0;
}
这样就会克服线程随机调度问题
短短的一篇~万圣节快乐捏(金工实习好累)