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.rhkb.cn/news/319311.html

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

相关文章

多多搜索推广计划怎么设置

拼多多推广可以使用3an推客。3an推客&#xff08;CPS模式&#xff09;给商家提供的营销工具&#xff0c;由商家自主设置佣金比例&#xff0c;激励推广者去帮助商家推广商品链接&#xff0c;按最终有效交易金额支付佣金&#xff0c;不成交不扣费。是商家破零、积累基础销量的重要…

自动化机器学习——网格搜索法:寻找最佳超参数组合

自动化机器学习——网格搜索法&#xff1a;寻找最佳超参数组合 在机器学习中&#xff0c;选择合适的超参数是模型调优的关键步骤之一。然而&#xff0c;由于超参数的组合空间通常非常庞大&#xff0c;手动调整超参数往往是一项耗时且困难的任务。为了解决这个问题&#xff0c;…

连接HiveMQ代理器实现MQTT协议传输

先下载MQTTX: MQTTX: Your All-in-one MQTT Client Toolbox 使用线上免费的MQTTX BROKER:The Free Global Public MQTT Broker | Try Now | EMQ 打开MQTTX&#xff0c;创建连接&#xff0c;点击NEW SUBSCRIPTION,创建一个主题&#xff0c;这里使用test/topic,在下面Json中填写…

使用 ORPO 微调 Llama 3

原文地址&#xff1a;https://towardsdatascience.com/fine-tune-llama-3-with-orpo-56cfab2f9ada 更便宜、更快的统一微调技术 2024 年 4 月 19 日 ORPO 是一种新的令人兴奋的微调技术&#xff0c;它将传统的监督微调和偏好校准阶段合并为一个过程。这减少了训练所需的计算…

Java零基础入门到精通_Day 8

1.API 应用程序接口 Java API:指的就是JDK 中提供的各种功能的Java类这些类将底层的实现封装了起来&#xff0c;我们不需要关心这些类是如何实现的&#xff0c;只需要学习这些类如何使用即可&#xff0c;我们可以通过帮助文档来学习这些API如何使用。 2. String String 类…

【副本向】Lua副本逻辑

副本生命周期 OnCopySceneTick() 子线程每次心跳调用 --副本心跳 function x3323_OnCopySceneTick(elapse)if x3323_g_IsPlayerEnter 0 thenreturn; -- 如果没人进入&#xff0c;则函数直接返回endif x3323_g_GameOver 1 thenif x3323_g_EndTick > 0 thenx3323_CountDown…

循环神经网络完整实现(Pytorch 13)

一 循环神经网络的从零开始实现 从头开始基于循环神经网络实现字符级语言模型。 %matplotlib inline import math import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2lbatch_size, num_steps 32, 35 train_iter, vocab …

分布式websocket IM即时通讯聊天开源项目如何启动

前言 自己之前分享了分布式websocket的视频有同学去fork项目了&#xff0c;自己启动一下更方便理解项目嘛。然后把项目启动需要的东西全部梳理出来。支持群聊单聊,表情包以及发送图片。 支持消息可靠&#xff0c;消息防重&#xff0c;消息有序。同时基础架构有分布式权限&…

OneFlow深度学习框原理、用法、案例和注意事项

本文将基于OneFlow深度学习框架&#xff0c;详细介绍其原理、用法、案例和注意事项。OneFlow是由中科院计算所自动化研究所推出的深度学习框架&#xff0c;专注于高效、易用和扩展性强。它提供了一种类似于深度学习库的接口&#xff0c;可以用于构建神经网络模型&#xff0c;并…

Android4.4真机移植过程笔记(三)

如果文章字体看得不是很清楚&#xff0c;大家可以下载pdf文档查看&#xff0c;文档已上传&#xff5e;oo&#xff5e; 7、安装加密APK 需要修改文件如下&#xff1a; 相对Android4.2改动还是蛮大的&#xff0c;有些文件连路径都变了: //Android4.2 1、frameworks/native/libs…

STL速查

容器 (Containers) 图解容器 支持随机访问 stringarrayvectordeque支持支持支持支持 string 类 构造函数 string(); ------创建一个空的字符串 例如: string str;string(const char* s); ------使用字符串s初始化string(const string& str); ------拷贝构造 赋值操作…

C++学习--点滴记录011

11函数提高 11.1 函数默认参数 在C中&#xff0c;函数的形参列表中的形参可以有默认值 语法&#xff1a; 返回值类型 函数名 &#xff08;参数 默认值&#xff09;{} 示例&#xff1a; #include <iostream> using namespace std;int func(int a, int b 10, int c …

网络基础-网络设备介绍

本系列文章主要介绍思科、华为、华三三大厂商的网络设备 网络设备 网络设备是指用于构建和管理计算机网络的各种硬件设备和设备组件。以下是常见的网络设备类型&#xff1a; 路由器&#xff08;Router&#xff09;&#xff1a;用于连接不同网络并在它们之间转发数据包的设备…

wpf线程中更新UI的4种方式

在wpf中&#xff0c;更新UI上面的数据&#xff0c;那是必经之路&#xff0c;搞不好&#xff0c;就是死锁&#xff0c;或者没反应&#xff0c;很多时候&#xff0c;都是嵌套的非常深导致的。但是更新UI的方式&#xff0c;有很多的种&#xff0c;不同的方式&#xff0c;表示的意思…

国内各种免费AI聊天机器人(ChatGPT)推荐(中)

作者主页&#xff1a;点击&#xff01; 国内免费AI推荐(ChatGPT)专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年4月29日15点20分 随着人工智能技术的不断发展&#xff0c;AI聊天机器人已经逐渐融入我们的日常生活。它们可以提供各种服务&#xff0c;例如聊天、…

Golang | Leetcode Golang题解之第68题文本左右对齐

题目&#xff1a; 题解&#xff1a; // blank 返回长度为 n 的由空格组成的字符串 func blank(n int) string {return strings.Repeat(" ", n) }func fullJustify(words []string, maxWidth int) (ans []string) {right, n : 0, len(words)for {left : right // 当前…

leetCode72. 编辑距离

leetCode72. 编辑距离 基本思路&#xff1a; 代码 class Solution { public:int minDistance(string a, string b) {// a,b的0不做表示&#xff0c;所以从1开始&#xff0c;dp状态表示&#xff0c;这种办法会很方便a a, b b;int n a.size();int m b.size(); // 定…

Elasticsearch 数据聚合

Bucket聚合&#xff08;桶聚合&#xff09; 对文档做分组&#xff0c;aggs 按照文档字段值或日期进行分组&#xff0c;能参与分词的字段不能做聚合&#xff0c;如text类型的字段 例如&#xff1a;根据城市名称做聚合&#xff0c;也就是城市名称对数据进行分组统计。可以加qu…

Sarcasm detection论文解析 |使用基于多头注意力的双向 LSTM 进行讽刺检测

论文地址 论文地址&#xff1a;https://ieeexplore.ieee.org/document/8949523 论文首页 笔记框架 使用基于多头注意力的双向 LSTM 进行讽刺检测 &#x1f4c5;出版年份:2020 &#x1f4d6;出版期刊:IEEE Access &#x1f4c8;影响因子:3.9 &#x1f9d1;文章作者:Kumar Avinas…

Educational Codeforces Round 165 (Rated for Div. 2) (C、D)

1969C - Minimizing the Sum 题意&#xff1a; 思路&#xff1a;观察到操作数很小&#xff0c;最值问题操作数很容易想到dp&#xff0c;用表示第个元素&#xff0c;操作了次的最小值总和&#xff0c;转移的时候枚举连续操作了几次即可&#xff0c;而连续操作了几次即将全部变成…