43 单例模式

目录

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.mzph.cn/pingmian/6500.shtml

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

相关文章

线性数据结构-手写队列-哈希(散列)Hash

什么是hash散列&#xff1f; 哈希表的存在是为了解决能通过O(1)时间复杂度直接索引到指定元素。这是什么意思呢&#xff1f;通过我们使用数组存放元素&#xff0c;都是按照顺序存放的&#xff0c;当需要获取某个元素的时候&#xff0c;则需要对数组进行遍历&#xff0c;获取到指…

【skill】onedrive的烦人问题

Onedrive的迷惑行为 安装Onedrive&#xff0c;如果勾选了同步&#xff0c;会默认把当前用户的数个文件夹&#xff08;桌面、文档、图片、下载 等等&#xff09;移动到安装时提示的那个文件夹 查看其中的一个文件的路径&#xff1a; 这样一整&#xff0c;原来的文件收到严重影…

吴恩达2022机器学习专项课程C2(高级学习算法)W1(神经网络):2.1神经元与大脑

目录 神经网络1.初始动机*2.发展历史3.深度学习*4.应用历程 生物神经元1.基本功能2.神经元的互动方式3.信号传递与思维形成4.神经网络的形成 生物神经元简化1.生物神经元的结构2.信号传递过程3.生物学术语与人工神经网络 人工神经元*1.模型简化2.人工神经网络的构建3.计算和输入…

Java与Go: 生产者消费者模型

什么是生产者消费者模型 生产者-消费者模型&#xff08;也称为生产者-消费者问题&#xff09;是一种常见的并发编程模型&#xff0c;用于处理多线程或多进程之间的协同工作。该模型涉及两个主要角色&#xff1a;生产者和消费者&#xff0c;一个次要角色&#xff1a;缓冲区。 生…

18 内核开发-内核重点数据结构学习

课程简介&#xff1a; Linux内核开发入门是一门旨在帮助学习者从最基本的知识开始学习Linux内核开发的入门课程。该课程旨在为对Linux内核开发感兴趣的初学者提供一个扎实的基础&#xff0c;让他们能够理解和参与到Linux内核的开发过程中。 课程特点&#xff1a; 1. 入门级别&…

办公数据分析利器:Excel与Power Query透视功能

数据分析利器&#xff1a;Excel与Power Query透视功能 Excel透视表和Power Query透视功能是强大的数据分析工具&#xff0c;它们使用户能够从大量数据中提取有意义的信息和趋势&#xff0c;可用于汇总、分析和可视化大量数据。 本文通过示例演示Power Query透视功能的一个小技…

Linux专栏08:Linux基本指令之压缩解压缩指令

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Linux专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Linux基本指令之压缩解压缩指令 编号&#xff1a;08 文章目录 Linu…

Spring Boot与OpenCV:融合机器学习的智能图像与视频处理平台

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

【模板】二维前缀和

原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 二维前缀和板题。 二维前缀和&#xff1a;pre[i][j]a[i][j]pre[i-1][j]pre[i][j-1]-pre[i-1][j-1]; 子矩阵 左上角为(x1,y1) 右下角(x2,y2…

PG控制文件的管理与重建

一.控制文件位置与大小 逻辑位置&#xff1a;pgpobal 表空间中 物理位置&#xff1a;$PGDATA/global/pg_control --pg_global表空间的物理位置就在$PGDATA/global文件夹下 物理大小&#xff1a;8K 二.存放的内容 1.数据库初始化的时候生成的永久化参数&#xff0c;无法更改…

brpc中http2 grpc协议解析

搭建gRPC服务 | bRPC https://blog.csdn.net/INGNIGHT/article/details/132657099 global.cpp http2_rpc_protocol.cpp ParseH2Message解析frame header信息 ParseResult H2Context::ConsumeFrameHead( 这个是固定长度的9字节帧头部&#xff0c;length是&#xff0c;3*8bit…

Mysql技能树学习

查询进阶 别名 MySQL支持在查询数据时为字段名或表名指定别名&#xff0c;指定别名时可以使用AS关键字。 BETWEEN AND条件语句 mysql> SELECT * FROM t_goods WHERE id BETWEEN 6 AND 8; 查询特定数据 &#xff08;CASE&#xff09; select name,case when price <…

Linux 麒麟系统安装

国产麒麟系统官网地址&#xff1a; https://www.openkylin.top/downloads/ 下载该镜像后&#xff0c;使用VMware部署一个虚拟机&#xff1a; 完成虚拟机创建。点击&#xff1a;“开启此虚拟机” 选择“试用试用开放麒麟而不安装&#xff08;T&#xff09;”&#xff0c;进入op…

Cisco Firepower FTD生成troubleshooting File

在出现故障时&#xff0c;需要采集信息 FMC上需要采集对应FTD设备的troubleshooting file system -->health -->monitor 选择相应的FTD&#xff0c;右侧点 generate Generate 4 右上角小红点点开 选择里面的task,就可以看到进度&#xff0c;差不多要10分钟以上 5 完成后…

基于51单片机的交通灯设计—可调时间、夜间模式

基于51单片机的交通灯设计 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.四方向数码管同时显示时间&#xff1b; 2.LED作红、绿、黄灯 3.三个按键可以调整红绿灯时间&#xff1b; 4.夜间模式&am…

IDEA上文件换行符、分隔符(Line Separator)LF,CR,CRLF错乱影响Git上传Github或Gitee代码

IDEA上文件换行符、分隔符(Line Separator)LF&#xff0c;CR&#xff0c;CRLF错乱影响Git上传Github或Gitee代码 指定目录 然后就可以上传了 OK 一定注意更改Line Separator的文件目录 如果是target目录下的文件,是不能修改为LF的,把target文件删除,再重载一次main文件,就…

FFmpeg学习记录(二)—— ffmpeg多媒体文件处理

1.日志系统 常用的日志级别&#xff1a; AV_LOG_ERRORAV_LOG_WARNINGAV_LOG_INFOAV_LOG_DEBUG #include <stdio.h> #include <libavutil/log.h>int main(int argc, char *argv[]) {av_log_set_level(AV_LOG_DEBUG);av_log(NULL, AV_LOG_DEBUG, "hello worl…

【软考高项】三十一、成本管理4个过程

一、规划成本管理 1、定义、作用 定义&#xff1a;确定如何估算、预算、管理、监督和控制项目成本的过程作用&#xff1a;在整个项目期间为如何管理项目成本提供指南和方向 应该在项目规划阶段的早期就对成本管理工作进行规划&#xff0c;建立各成本管理过程的基本框架&…

RKNN Toolkit2 工具的使用

RKNN Toolkit2 是由瑞芯微电子 (Rockchip) 开发的一套用于深度学习模型优化和推理的工具。它主要面向在瑞芯微SoC上进行AI应用开发&#xff0c;但也可以用于PC平台进行模型的转换、量化、推理等操作。它支持将多种深度学习框架的模型&#xff08;如Caffe, TensorFlow, PyTorch等…

单例、工厂、策略、装饰器设计模式

1. 单例模式&#xff08;Singleton Pattern&#xff09;&#xff1a; 单例模式是一种常用的设计模式&#xff0c;用于确保一个类只有一个实例&#xff0c;并提供一个全局访问点。这种模式的特点是类自己负责保存其唯一的实例&#xff0c;并控制其实例化过程。单例模式广泛应用…