Bootstrap

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;
}

这样就会克服线程随机调度问题        

短短的一篇~万圣节快乐捏(金工实习好累)

;