43 单例模式

news2024/12/28 19:07:14

目录

1.什么是单例模式
2.什么是设计模式
3.特点
4.饿汉和懒汉
5.峨汉实现单例
6.懒汉实现单例
7.懒汉实现单例(线程安全)
8.STL容器是否线程安全
9.智能指针是否线程安全
10.其他常见的锁
11.读者写者问题

1. 什么是单例模式

单例模式是一种经典的,常用的,常考的设计模式

2. 什么是设计模式

针对一些常用场景,给定了相应的解决方案,这个就是设计模式

3. 特点

一个类只能具有一个对象就是单例。在很多服务器开发中,经常要让服务器数据加载到上百G内存中,往往用一个单例的类管理这些数据

4. 饿汉和懒汉

吃完饭,立刻洗碗,就是峨汉方式,下一顿吃的时候就可以立刻拿着碗吃
吃完饭,先放下,下一顿吃的时候再洗碗,就是懒汉

懒汉的核心思想是“延时加载”,从而优化服务器的启动速度,因为加载时需要加载的东西少了,到实际使用时再加载,只是调整了花费时间的比例

5. 峨汉实现单例

template
class Singleton {
static T data;
public:
static T* GetInstance() {
return &data;
}
};

只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例

6. 懒汉实现单例

template
class Singleton {
static T* inst;
public:
static T* GetInstance() {
if (inst == NULL) {
inst = new T();
}
return inst;
}
};

存在一个严重的问题,线程不安全。第一次调用GetInstance时,如果两个线程同时调用,可能会创建两份T对象实例,但是后续再调用,就没有问题了

7. 懒汉实现单例(线程安全)

将前面的线程池修改为单例模式。将构造函数都私有,定义一个类指针,只需要一个变量所以用static,用一个static函数获取这个指针,如果为空就new一个。如果多线程并发访问有可能会生成多个对象,所以用一个静态的锁对判断加锁,不是每次都需要加锁,再套一层判断,只有为空时才加锁

// 懒汉模式, 线程安全
template
class Singleton {
volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.
static std::mutex lock;
public:
static T* GetInstance() {
if (inst == NULL) { // 双重判定空指针, 降低锁冲突的概率, 提高性能.
lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new.
if (inst == NULL) {
inst = new T();
}
lock.unlock();
}
return inst;
}
};

#pragma once
#include <vector>
#include <queue>
#include <pthread.h>
#include <string>
#include <unistd.h>

//换为封装的线程
struct ThreadInfo
{
    pthread_t _tid;
    std::string _name;
};
template <class T>
class pool
{
    static const int defaultnum = 5;

public:
   

    std::string getname(pthread_t tid)
    {
        for (auto ch : _thread)
        {
            if (ch._tid == tid)
            {
                return ch._name;
            }
        }
        return "None";
    }
    static void* HandlerTask(void* args)
    {
        pool<T> *tp = static_cast<pool<T> *>(args);
        std::string name = tp->getname(pthread_self());
        while (true)
        {
            pthread_mutex_lock(&(tp->_mutex));
            while (tp->_que.empty())
            {
                pthread_cond_wait(&(tp->_cond), &(tp->_mutex));
            }
            T t = tp->_que.front();
            tp->_que.pop();
            pthread_mutex_unlock(&tp->_mutex);
            t.run();
            printf("%s finsih task:%s\n", name.c_str(), t.getresult().c_str());
            sleep(1);
        }
        }
    void start()
    {
        for (int i = 0; i < _thread.size(); i++)
        {
            _thread[i]._name = "thread" + std::to_string(i);
            pthread_create(&_thread[i]._tid, nullptr, HandlerTask, this);
        }
    }

    void push(const T& x)
    {
        pthread_mutex_lock(&_mutex);
        _que.push(x);
        pthread_cond_signal(&_cond);
        pthread_mutex_unlock(&_mutex);
    }
    
    static pool<T>* GetInstance()
    {
        //套一层判断,只有第一次需要上锁
        if (_pl == nullptr)
        {
            pthread_mutex_lock(&_lock);
            if (_pl == nullptr)
            {
                printf("first create\n");
                _pl = new pool<T>;
            }
            pthread_mutex_unlock(&_lock);
        }

        return _pl;
    }

private:
//构造私有化
    pool(int num = defaultnum)
        : _thread(num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }

    pool(const pool<T> &) = delete;
    const pool<T> &operator=(const pool<T>&) = delete;
    ~pool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }
    std::vector<ThreadInfo> _thread;
    std::queue<T> _que;

    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

    static pthread_mutex_t _lock;
    static pool<T> *_pl;
};

//类外初始化
template <class T>
pool<T>* pool<T>::_pl = nullptr;
template <class T>
pthread_mutex_t pool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;

使用

pool::GetInstance()->start();

注意事项:
1.加解锁的位置
2.双重if判断,避免不必要的锁竞争
3.volatile关键字防止过度优化

8. STL容器是否线程安全

不是
STL的设计初衷是将性能挖掘到极致,一旦涉及到加锁保证线程安全,会对性能产生巨大影响,而且对于不同的容器,枷锁方式的不同,性能可能也不同(例如hash表的锁表和锁桶)
因此STL默认不是线程安全的,如果需要再多线程的环境下使用,需要调用者自行保证线程安全

9. 智能指针是否线程安全

对于unique_ptr,由于只是当前代码范围内生效,所以不涉及线程安全的问题
对于shared_ptr,多个对象需要共用一个引用计数变量,所以会存在线程安全问题,但是标准库实现的时候考虑了这个问题,基于原子操作CAS)的方式保证shared_ptr能狗高效,原子的操作引用计数

10. 其他常见的锁

悲观锁:每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,解锁等),当其他线程想要访问数据时,被阻塞挂起
乐观锁:每次取数据时,总是乐观的认为数据不会被其他线程修改,所以不上锁,但是在更新数据前,会判断其他数据在更新前有没有对数据修改。主要采取两种方式:版本号机制和CAS操作
CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新,若不相等则失败,失败则重试,一般是一个自旋的过程,即不断重试
自旋锁,公平锁,非公平锁

当申请一种资源失败后,线程就会挂起,如果是非阻塞加锁就会返回。如果临界区的访问特别快,就没有必要挂起线程,可以不断尝试申请资源,这种就是自旋锁。用自旋锁还是挂起锁取决于临界区执行时长
自旋锁相关函数,和其他锁类似
在这里插入图片描述

在这里插入图片描述在这里插入图片描述

11. 读者写者问题

在编写多线程的时候,有一种情况十分常见。有些公共数据修改的机会比较少,相较于写,读的机会反而高的多。通常而言,读的过程伴随着查找的操作,消耗的时间很长,给这段代码枷锁,会极大的降低效率,有没有处理这种多读少写的情况?就是读写锁(长时间等人和短时间等人的例子)

比如公告这种,写的人很少,读的人很多。可以多个读者同时访问公共资源,原因就是不会取走数据

321原则:
3种关系:读读(共享),读写(互斥,同步),写写(互斥竞争)
2种角色:读者,写者
1个交易场所:数据交换的地点

两种策略:
读写情况,读者访问的几率大的情况是正常现象
读者优先:当读者和写者要同时访问共享资源,所有读者访问完写者再访问
写者优先:当同时访问时,等待内部的写者写完,写者先进去,然后读者再进来

读写锁行为

当前锁状态读锁请求写锁请求
无锁可以可以
读锁可以阻塞
写锁阻塞阻塞

写独占,读共享,写锁优先级高

相关函数
设置优先

int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t attr, int pref);
/

pref 共有 3 种选择
PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和
PTHREAD_RWLOCK_PREFER_READER_NP 一致
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁
*/

初始化

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t
*restrict attr);

销毁

int pthread _rwlock_destroy(pthread_rwlock_t *rwlock);

加锁和解锁

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

伪代码
在这里插入图片描述

当第一位读者访问时,如果有写者,先让写者写完,然后所有读者都来访问,最后一个读者访问完后释放写锁。写者互斥访问写入

案例

#include <vector>
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
volatile int ticket = 1000;
pthread_rwlock_t rwlock;
void * reader(void * arg)
{
char *id = (char *)arg;
while (1) {
pthread_rwlock_rdlock(&rwlock);
if (ticket <= 0) {
pthread_rwlock_unlock(&rwlock);
break;
}
printf("%s: %d\n", id, ticket);
pthread_rwlock_unlock(&rwlock);
usleep(1);
}
return nullptr;
}
void * writer(void * arg)
{
char *id = (char *)arg;
while (1) {
pthread_rwlock_wrlock(&rwlock);
if (ticket <= 0) {
pthread_rwlock_unlock(&rwlock);
break;
}
printf("%s: %d\n", id, --ticket);
pthread_rwlock_unlock(&rwlock);
usleep(1);
}
return nullptr;
}
struct ThreadAttr
{
pthread_t tid;
std::string id;
};
std::string create_reader_id(std::size_t i)
{
// 利用 ostringstream 进行 string 拼接
std::ostringstream oss("thread reader ", std::ios_base::ate);
oss << i;
return oss.str();
}
std::string create_writer_id(std::size_t i)
{
// 利用 ostringstream 进行 string 拼接
std::ostringstream oss("thread writer ", std::ios_base::ate);
oss << i;
return oss.str();
}
void init_readers(std::vector<ThreadAttr>& vec)
{
for (std::size_t i = 0; i < vec.size(); ++i) {
vec[i].id = create_reader_id(i);
pthread_create(&vec[i].tid, nullptr, reader, (void *)vec[i].id.c_str());
}
}
void init_writers(std::vector<ThreadAttr>& vec)
{
for (std::size_t i = 0; i < vec.size(); ++i) {
vec[i].id = create_writer_id(i);
pthread_create(&vec[i].tid, nullptr, writer, (void *)vec[i].id.c_str());
}
}
void join_threads(std::vector<ThreadAttr> const& vec)
{
// 我们按创建的 逆序 来进行线程的回收
for (std::vector<ThreadAttr>::const_reverse_iterator it = vec.rbegin(); it !=
vec.rend(); ++it) {
pthread_t const& tid = it->tid;
pthread_join(tid, nullptr);
}
}
void init_rwlock()
{
#if 0 // 写优先
pthread_rwlockattr_t attr;
pthread_rwlockattr_init(&attr);
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
pthread_rwlock_init(&rwlock, &attr);
pthread_rwlockattr_destroy(&attr);
#else // 读优先,会造成写饥饿
pthread_rwlock_init(&rwlock, nullptr);
#endif
}
int main()
{
// 测试效果不明显的情况下,可以加大 reader_nr
// 但也不能太大,超过一定阈值后系统就调度不了主线程了
const std::size_t reader_nr = 1000;
const std::size_t writer_nr = 2;
std::vector<ThreadAttr> readers(reader_nr);
std::vector<ThreadAttr> writers(writer_nr);
init_rwlock();
init_readers(readers);
init_writers(writers);
join_threads(writers);
join_threads(readers);
pthread_rwlock_destroy(&rwlock);
}

只能看到写饥饿

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1642485.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

保研面试408复习 1——操作系统、计网、计组

文章目录 1、操作系统一、操作系统的特点和功能二、中断和系统调用的区别 2、计算机组成原理一、冯诺依曼的三个要点二、MIPS&#xff08;每秒百万条指令&#xff09;三、CPU执行时间和CPI 3、计算机网络一、各个层常用协议二、网络协议实验——数据链路层a.网络速率表示b.数据…

机器学习的两种典型任务

机器学习中的典型任务类型可以分为分类任务&#xff08;Classification&#xff09;和回归任务&#xff08;Regression&#xff09; 分类任务 回归任务 简单的理解&#xff0c;分类任务是对离散值进行预测&#xff0c;根据每个样本的值/特征预测该样本属于类 型A、类型B 还是类…

迎接AI时代:智能科技的社会责任与未来展望

AI智能体的社会角色、伦理挑战与可持续发展路径 引言&#xff1a; 在技术的浪潮中&#xff0c;AI智能体正逐步成为我们生活的一部分。它们在医疗、教育、交通等领域的应用&#xff0c;预示着一个全新的时代即将到来。本文将结合实际案例和数据分析&#xff0c;深入探讨AI智能体…

JavaWeb请求响应概述

目录 一、请求响应流程-简述 二、深入探究 三、DispatcherServlet 四、请求响应流程-详细分析 一、请求响应流程-简述 web应用部署在tomcat服务器中&#xff0c;前端与后端通过http协议进行数据的请求和响应。前端通过http协议向后端发送数据请求&#xff0c;就可以访问到部…

Amazon EKS创建S3数据存储卷

亚马逊相关文档 1、创建适用于 Amazon S3的IAM策略 创建存储桶amazoneks {"Version": "2012-10-17","Statement": [{"Effect": "Allow","Action": "s3express:CreateSession","Resource": &…

docker部署nginx并配置https

1.准备SSL证书&#xff1a; 生成私钥&#xff1a;运行以下命令生成一个私钥文件。 生成证书请求&#xff08;CSR&#xff09;&#xff1a;运行以下命令生成证书请求文件。 生成自签名证书&#xff1a;使用以下命令生成自签名证书。 openssl genrsa -out example.com.key 2048 …

SpringCloud微服务:Eureka 和 Nacos 注册中心

共同点 都支持服务注册和服务拉取都支持服务提供者心跳方式做健康检测 不同点 Nacos 支持服务端主动检测提供者状态&#xff1a;临时实例采用心跳模式&#xff0c;非临时&#xff08;永久&#xff09;实例采用主动检测模式Nacos 临时实例心跳不正常会被剔除&#xff0c;非临时实…

【LLM第二篇】stable diffusion扩散模型、名词解释

最近在整理大模型的相关资料&#xff0c;发现了几个名词&#xff0c;不是很懂&#xff0c;这里整理一下&#xff1a; stable diffusion&#xff08;SD)模型&#xff1a; 扩散模型&#xff08;Diffusion model&#xff09;的一种&#xff0c;主要用于生成高质量的图像&#xf…

Web后端开发中对三层架构解耦之控制反转与依赖注入

内聚与耦合 内聚 比如说我们刚刚书写的员工的实现类 在这里我们仅仅书写的是和员工相关的代码 而与员工无关的代码都没有放到这里 说明内聚程度较高 耦合 以后软件开发要高内聚 低耦合 提高程序灵活性 扩拓展性 分析代码 如何解耦 创建容器 提供一个容器 存储东西 存储E…

计算机毕业设计Python+Spark考研预测系统 考研推荐系统 考研数据分析 考研大数据 大数据毕业设计 大数据毕设

安顺学院本科毕业论文(设计)题目申请表 院别&#xff1a;数学与计算机科学 专业&#xff1a;数据科学与大数据 时间&#xff1a;2022年 5月26日 题 目 情 况 题目名称 基于hive数据仓库的考研信息离线分析系统的设计与实现 学生姓名 杨娣荧 学号 201903144042 …

springmvc下

第二类初始化操作 multipartResolver应用 localeResolver应用 themeResolver应用 handlerMapping应用 handlerAdapter应用 handlerExceptionReslver requestToViewNameTranslator应用 viewResolver应用 flashMapManager应用 dispatcherServlet逻辑处理 processRequest处理web请…

Llama改进之——SwiGLU激活函数

引言 今天介绍LLAMA模型引入的关于激活函数的改进——SwiGLU1&#xff0c;该激活函数取得了不错的效果&#xff0c;得到了广泛地应用。 SwiGLU是GLU的一种变体&#xff0c;其中包含了GLU和Swish激活函数。 GLU GLU(Gated Linear Units,门控线性单元)2引入了两个不同的线性层…

JVM知识总汇(JVM面试题篇5.1)

个人理解&#xff0c;所学有限&#xff0c;若有不当&#xff0c;还请指出 1.JVM是由哪些部分组成&#xff0c;运行流程是什么&#xff1f; JVM为java虚拟机&#xff0c;是java程序的运行环境&#xff08;其实是java字节码文件的运行环境&#xff09;&#xff0c;能够实现一次编…

【LinuxC语言】信号相关函数——kill、raise、pause与alarm

文章目录 前言一、函数介绍1.1 kill() 函数1.2 raise() 函数1.3 pause() 函数1.4 alarm() 函数 总结 前言 在Linux环境下&#xff0c;信号是一种重要的进程间通信机制&#xff0c;用于处理异步事件和控制进程行为。除了使用signal函数来设置信号处理函数外&#xff0c;还有一些…

初识C语言——第九天

ASCII定义 在 C 语言中&#xff0c;每个字符都对应一个 ASCII 码。ASCII 码是一个字符集&#xff0c;它定义了许多常用的字符对应的数字编码。这些编码可以表示为整数&#xff0c;也可以表示为字符类型。在 C 语言中&#xff0c;字符类型被定义为一个整数类型&#xff0c;它占…

C语言学习【最基本】

C语言学习 简单的 C 程序示例 #include "stdio.h" /* 提供键盘输入与屏幕输出支持 */ /* 相当于把stdio.h文件中的所有内容都输入到该行所在位置 拷贝-粘贴 *//* void 表示不带任何参数 */ int main(void) /* 函数名 */ { …

【跟我学RISC-V】(二)RISC-V的基础知识学习与汇编练习

写在前面&#xff1a; 这篇文章是跟我学RISC-V的第二期&#xff0c;是第一期的延续&#xff0c;第一期主要是带大家了解一下什么是RISC-V,是比较大体、宽泛的概念。这一期主要是讲一些基础知识&#xff0c;然后进行RISC-V汇编语言与c语言的编程。在第一期里我们搭建了好几个环…

虚拟化技术 安装并配置ESXi服务器系统

安装并配置ESXi服务器系统 一、实验目的与要求 1.掌握创建VMware ESXi虚拟机 2.掌握安装VMware ESXi系统 3.掌握配置VMware ESXi系统的管理IP 4.掌握开启VMware ESXi的shell和ssh功能的方法 二、实验内容 1.安装VMware workstation 15或更高版本 2.创建VMware ESXi虚拟…

Python数据分析案例44——基于模态分解和深度学习的电负荷量预测(VMD+BiGRU+注意力)

案例背景 承接之前的案例&#xff0c;说要做模态分解加神经网络的模型的&#xff0c;前面纯神经网络的缝合模型参考数据分析案例41和数据分析案例42。 虽然我自己基于各种循环神经网络做时间序列的预测已经做烂了.....但是还是会有很多刚读研究生或者是别的领域过来的小白来问…

【算法与数据结构】哈希表

文章目录 引入哈希函数介绍便利店的例子Python3 中的哈希表C 中的哈希表 应用将散列表用于查找防止重复将散列表用作缓存 哈希冲突与解决链地址法开放寻址 总结参考资料写在最后 引入 假设你在一家便利店上班&#xff0c;你不熟悉每种商品的价格&#xff0c;在顾客需要买单是时…