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

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

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

相关推荐
影视飓风TIM9 小时前
Linux 进程信号原理与剖析
linux·运维·服务器
缘友一世9 小时前
开源 Linux截图工具闪构(SnapForge)
linux·运维·ubuntu·开源·截图
朱包林10 小时前
iTerm2终端配置指南
linux·javascript·python·云原生·容器·kubernetes·云计算
祖力5511 小时前
Linux系统移植、烧录与环境的搭建
linux·运维·服务器
张小姐的猫11 小时前
【Linux】网络编程 —— 多路转接:epoll
linux·运维·网络
宋明河12 小时前
五种 IO 模型与 select、poll、epoll 对比
linux
玄芯散人12 小时前
【金丹·81】性能分析:你的程序慢在哪
linux·性能优化·perf
天意__12 小时前
Linux部署Qwen轻量模型
linux·服务器·qwen
木卫二号Coding12 小时前
第八十六篇-Strata 部署指南(Ubuntu 22.04 + Tesla V100【单卡】)
linux·运维·人工智能·ubuntu
11路没有终点12 小时前
sed 命令详解:文本编辑流处理实战
linux