当前位置: 首页 > news >正文

STL,智能指针和线程安全,线程安全的单例模式和懒汉饿汉的实现,以及读者写者问题

🍑个人主页:Jupiter.
🚀 所属专栏:Linux从入门到进阶
欢迎大家点赞收藏评论😊

在这里插入图片描述

在这里插入图片描述

目录

  • `📚STL,智能指针和线程安全 `
    • `📕STL中的容器是否是线程安全的?`
    • `💡智能指针是否是线程安全的?`
      • `🍁shared_ptr、unique_ptr和weak_ptr`
      • `🍑线程安全的智能指针atomic_shared_ptr`
  • `🌙线程安全的单例模式 `
            • `什么是单例模式 `
            • `什么是设计模式`
            • `单例模式的特点 `
    • `⭐饿汉实现方式和懒汉实现方式`
      • `🦅饿汉方式实现单例模式 `
      • `🦌懒汉方式实现单例模式`
      • `🐎懒汉方式实现单例模式(线程安全版本)`
  • 🐛其他常见的各种锁
            • `什么是自旋?`
  • `📚读者写者问题`
    • `🔒读写锁 `
      • 🐟系统中读写锁接口
    • 🦈读者写者优先问题:


📚STL,智能指针和线程安全

📕STL中的容器是否是线程安全的?

  • 不是.

  • 原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响。

  • 而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).

  • 因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.

💡智能指针是否是线程安全的?

🍁shared_ptr、unique_ptr和weak_ptr

在C++中,常见的智能指针包括shared_ptrunique_ptrweak_ptr

  • shared_ptr:

    • shared_ptr使用引用计数机制来管理对象的生命周期。虽然它的复制构造函数和赋值操作是原子操作(这保证了智能指针本身的拷贝在多线程下是安全的),但引用计数的增加和减少通常不是原子的。这意味着,如果多个线程同时尝试增加或减少同一个shared_ptr的引用计数,而没有适当的同步机制,就可能导致数据竞争和不一致的问题。因此,在多线程环境中,直接使用shared_ptr进行共享对象的访问和修改通常不是线程安全的。如果需要在多线程中使用shared_ptr,应该使用互斥锁或其他同步机制来保护对共享资源的访问。
  • unique_ptr:

    • unique_ptr保证同一时间只有一个所有者拥有对象的所有权。由于它不允许复制(除了移动构造和移动赋值之外),因此它不会遇到与shared_ptr相同的线程安全问题。然而,如果尝试将unique_ptr传递给不同的线程,或者在多线程中修改它的所有权,仍然需要适当的同步措施来确保线程安全。
  • weak_ptr:

    • weak_ptr是一个对shared_ptr所管理的对象存在性的弱引用,它不会增加对象的引用计数。因此,weak_ptr的线程安全性问题通常与它所引用的shared_ptr的线程安全性相关。在多线程环境中,如果weak_ptr和shared_ptr一起使用,并且没有适当的同步机制,同样可能导致问题。

🍑线程安全的智能指针atomic_shared_ptr

为了解决多线程环境中智能指针的线程安全问题,C++14引入了atomic_shared_ptr。这是一个线程安全的智能指针,它使用原子操作来管理引用计数,从而避免了数据竞争的问题。然而,需要注意的是,即使使用了atomic_shared_ptr,对智能指针所指向对象的访问仍然可能需要额外的同步机制,特别是当这些访问涉及到修改对象状态时。


🌙线程安全的单例模式

什么是单例模式
  • 单例模式是一种 “经典的, 常用的, 常考的” 设计模式。
什么是设计模式

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

单例模式的特点

某些类, 只应该具有一个对象(实例), 就称之为单例。

在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据。

⭐饿汉实现方式和懒汉实现方式

[洗完的例子]

  • 吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭.
  • 吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.

饿汉方式优化将来使用的效率,以空间换时间。程序加载的时候就创建对象。

🦅饿汉方式实现单例模式

  • 只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例.
template <typename T> 
class Singleton 
{ 
public: static T* GetInstance() { return &data; } 
private:static T data; 
}; 

🦌懒汉方式实现单例模式

懒汉方式最核心的思想是 "延时加载". 从而能够优化服务器的启动速度.运行时创建对象。

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

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

🐎懒汉方式实现单例模式(线程安全版本)

// 懒汉模式, 线程安全 
template <typename T> 
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; } 
};

🐛其他常见的各种锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
  • CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
  • 自旋锁,公平锁,非公平锁?
  • 重复非阻塞的申请锁,称为自选,非阻塞的读取文件这个过程叫非阻塞
什么是自旋?
  • 如果一个线程申请锁成功后执行的代码很耗时(比如代码里面面有大量的io操作等),推荐线程在申请锁的时候等待挂起,如果不耗时的话,推荐让其他线程重复非阻塞的申请锁,也就是自旋

  • 自旋锁:用法和其他锁一样,不赘述。


📚读者写者问题

🔒读写锁

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。

读写锁的伪代码:

🐟系统中读写锁接口

  • 初始化
    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);

🦈读者写者优先问题:

  • 默认的是写者优先

写者优先:当读者正在访问资源的时候,写者申请访问,这时候过后再有读者申请锁的时候,会失败,类似于会放到在一个等待队列中等待,会等当前读者读完后,写者再访问资源,访问完后这些读者才会申请锁访问资源(也就是说,如果读者挡在访问资源的时候,这时写者要访问资源,后续来的读者都会等待,等目前正在访问资源的读者退出后,读者访问完资源退出后,这些等待的读者才可以申请锁,访问资源。)

读者优先:与上面的相反。


相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 如何用 Helm Chart 安装指定版本的 GitLab Runner?
  • 安卓手机安装Termux,变成linux服务器
  • Python 数学建模——Pearson/Spearman 相关系数
  • linux高级
  • 磁光阱的原理与搭建操作
  • 海康威视相机在QTcreate上的使用教程
  • pycharm 安装对应python版本的pip
  • Linux C高级 day1
  • SpringBoot2:web开发常用功能实现及原理解析-@ControllerAdvice实现全局异常统一处理
  • WPF动画
  • Vue学习记录之一(介绍及脚手架的使用)
  • spark学习笔记
  • 微信小程序页面制作——婚礼邀请函(含代码)
  • netstat和ss命令用法
  • 鹰眼降尘系统怎么样
  • $translatePartialLoader加载失败及解决方式
  • ComponentOne 2017 V2版本正式发布
  • DOM的那些事
  • Iterator 和 for...of 循环
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • Linux Process Manage
  • React16时代,该用什么姿势写 React ?
  • SAP云平台里Global Account和Sub Account的关系
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 原生js练习题---第五课
  • MyCAT水平分库
  • ​​快速排序(四)——挖坑法,前后指针法与非递归
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • ​第20课 在Android Native开发中加入新的C++类
  • ​七周四次课(5月9日)iptables filter表案例、iptables nat表应用
  • # SpringBoot 如何让指定的Bean先加载
  • #stm32驱动外设模块总结w5500模块
  • #我与Java虚拟机的故事#连载18:JAVA成长之路
  • #预处理和函数的对比以及条件编译
  • (003)SlickEdit Unity的补全
  • (2015)JS ES6 必知的十个 特性
  • (22)C#传智:复习,多态虚方法抽象类接口,静态类,String与StringBuilder,集合泛型List与Dictionary,文件类,结构与类的区别
  • (a /b)*c的值
  • (Repost) Getting Genode with TrustZone on the i.MX
  • (zz)子曾经曰过:先有司,赦小过,举贤才
  • (不用互三)AI绘画工具应该如何选择
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • .NET Standard 的管理策略
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • .NET6实现破解Modbus poll点表配置文件
  • .NetCore项目nginx发布
  • .NET的数据绑定
  • .Net中的集合
  • /etc/sudoer文件配置简析
  • ::前边啥也没有