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

只能看到写饥饿

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

243 基于matlab的模糊C均值算法(FCM)及其改进算法将空间邻域项引入FCM的目标函数(FCM_S)

基于matlab的模糊C均值算法&#xff08;FCM&#xff09;及其改进算法将空间邻域项引入FCM的目标函数(FCM_S),广义的模糊C均值(GFCM)算法&#xff0c;基于核的改进的模糊c均值聚类算法&#xff08;KFCM&#xff09;,基于核的广义模糊c均值聚类算法KGFCM的图像分割方法。程序已调…

一文了解python机器学习Sklearn

1.3 安装和配置Sklearn 要使用Sklearn库&#xff0c;首先需要安装Python和相应的库。在本教程中&#xff0c;我们将使用Python 3.x版本。可以使用以下命令安装Sklearn库&#xff1a; pip install scikit-learn安装完成后&#xff0c;可以在Python代码中导入Sklearn库&#xf…

【Android学习】自定义文本框和输入监听

实现功能 以上代码可实现功能&#xff1a; 1 自定义文本框样式 2. 文本框触发形式转变 3. 文本框输入长度监听&#xff0c;达到最大长度关闭软键盘 4. password框触发检测phone框内容 1. drawable自定义形状 我创建了editor_focus.xml 和 editor_unfocus.xml&#xff0c;两者仅…

猿人学第七题-动态字体-随风漂移

前言&#xff1a;该题主要是考对fontTools.ttLib.TTFont的操作&#xff0c;另外就是对字典互相映射的操作 一、woff文件存储 from fontTools.ttLib import TTFont #pip install fontTools def save_woff(response):woff response[woff]woff_file base64.b64decode(woff.enc…

K8S 哲学 - 服务发现 services

apiVersion: v1 kind: Service metadata:name: deploy-servicelabels:app: deploy-service spec: ports: - port: 80targetPort: 80name: deploy-service-podselector: app: deploy-podtype: NodePort 主机端口分配方式 两个 name port 和 targetPort type 类型

【实验】使用docker-compose编排lnmp(dockerfile) 完成Wordpress 部署

环境准备 docker&#xff1a;192.168.67.30 虚拟机&#xff1a;4核4G 关闭防火墙 systemctl stop firewalld systemctl disable firewalld setenforce 0 安装docker 直接点击【复制】粘贴到xshell中即可&#xff0c; 执行过程中若出现睡眠(sleep)通过 kill -9 pid号 &#x…

QT中的容器

Qt中的容器 关于Qt中的容器类&#xff0c;下面我们来进行一个总结&#xff1a; Qt的容器类比标准模板库&#xff08;STL&#xff09;中的容器类更轻巧、安全和易于使用。这些容器类是隐式共享和可重入的&#xff0c;而且他们进行了速度和存储的优化&#xff0c;因此可以减少可…

翔云优配恒生指数涨1.85%、恒生科技指数涨3.74% 小鹏汽车涨超8%

5月3日港股开盘&#xff0c;恒生指数涨1.85%&#xff0c;报18543.3点&#xff0c;恒生科技指数涨3.74%&#xff0c;报4009.96点&#xff0c;国企指数涨2.23%&#xff0c;报6580.81点&#xff0c; 翔云优配是一家领先的在线投资平台,提供全球范围内的股票、期货、基金等交易服务…

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

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

【Gateway远程开发】0.5GB of free space is necessary to run the IDE.

【Gateway远程开发】0.5GB of free space is necessary to run the IDE. 报错 0.5GB of free space is necessary to run the IDE. Make sure that there’s enough space in following paths: /root/.cache/JetBrains /root/.config/JetBrains 原因 下面两个路径的空间不…

《Fundamentals of Power Electronics》——基础交流建模方法

PWM整流器小信号交流模型建模的主要步骤为&#xff1a; (a)利用小纹波近似的动态版本&#xff0c;建立与电感和电容波形的低频平均值有关的方程&#xff1b; (b)平均方程的扰动和线性化&#xff1b; (c)交流等效电路模型的建立。 以下图buck-boost电路为例进行分析。 首先测…

深入学习和理解Django视图层:处理请求与响应

title: 深入学习和理解Django视图层&#xff1a;处理请求与响应 date: 2024/5/4 17:47:55 updated: 2024/5/4 17:47:55 categories: 后端开发 tags: Django请求处理响应生成模板渲染表单处理中间件异常处理 第一章&#xff1a;Django框架概述 1.1 什么是Django&#xff1f;…

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

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

hadoop学习---基于Hive的教育平台数据仓库分析案例(一)

案例背景&#xff1a; 大数据技术的应用可以从海量的用户行为数据中进行挖掘分析&#xff0c;根据分析结果优化平台的服务质量&#xff0c;最终满足用户的需求。教育大数据分析平台项目就是将大数据技术应用于教育培训领域&#xff0c;为企业经营提供数据支撑。 案例数据产生流…

Ubuntu20安装torch1.13和pytorch_geometric2.3.0(对应cuda11.6)

在torch下载页面搜索1.13https://pytorch.org/get-started/previous-versions/&#xff0c;wheel安装方式&#xff08;激活conda虚拟环境&#xff09; pip install torch1.13.0cu116 torchvision0.14.0cu116 torchaudio0.13.0 --extra-index-url https://download.pytorch.org…

快速构建vscode pytest 开发测试环境

如果不想用 heavy 的pycharm vscode 也是1个很好的选择 安装python SDK pacman -S python [gatemanmanjaro-x13 tmp]$ pacman -Q python python 3.11.8-1安装Vscode 很多中方法 yay -S visual-studio-code-bin [gatemanmanjaro-x13 tmp]$ pacman -Q | grep -i visual visua…

关于怎么计算重复路段的算法问题

这是代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS #define MAX_TEST 100 #include<stdio.h> int main() {int l, m;int u0, v0,j0;int arr[MAX_TEST][10] {0};int count0;scanf("%d", &l);scanf("%d", &m);if (l < 500 &…

性能优化(一):ArrayList还是LinkedList?

引言 集合作为一种存储数据的容器&#xff0c;是我们日常开发中使用最频繁的对象类型之一。JDK为开发者提供了一系列的集合类型&#xff0c;这些集合类型使用不同的数据结构来实现。因此&#xff0c;不同的集合类型&#xff0c;使用场景也不同。 很多同学在面试的时候&#x…

短视频矩阵系统ai剪辑 矩阵 文案 无人直播四合一功能核心独家源头saas开发

抖去推矩阵AI小程序是一款针对短视频平台的智能创作和运营工具&#xff0c;它具有以下功能特点&#xff1a; 1.批量视频生成&#xff1a;抖去推可以在短时间内生成大量视频&#xff0c;帮助商家快速制作出适合在短视频平台上推广的内容 2.全行业覆盖&#xff1a;适用于多个行业…

环形链表面试题详解

A. 环形链表1 给你一个链表的头节点 head &#xff0c;判断链表中是否有环. 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置…