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

Linux--线程安全的单例模式--自旋锁--0211

1. 线程安全的单例模式

1.1 什么是单例模式

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

1.1.1 懒汉方式实现单例模式

以上篇博文的线程池为例

Liunx--线程池的实现--0208 09_Gosolo!的博客-CSDN博客

  • 实现懒汉模式首先要先将构造函数私有化,并禁止生成默认的拷贝构造函数,赋值运算符重载,不能随意的构造出来对象。
  • 其次增加一个静态的线程池对象,才符合单例的条件。
  • 然后需要有一个函数可以用来构造对象,由于该函数需要知道静态线程池对象是否不为空,也就是需要访问静态成员,所以该函数也必须是静态的
template<class T>
class ThreadPool
{//...
private:ThreadPool(int thread_num=g_thread_num):num_(thread_num){//创造线程的空间 构造线程for(int i=1;i<=num_;i++){//每个线程的编号 回调函数 输出型参数threads_.push_back(new Thread(i,routine,this)); }pthread_mutex_init(&lock,nullptr);pthread_cond_init(&cond,nullptr);}ThreadPool(const ThreadPool<T>& other)=delete;const ThreadPool<T> operator=(const ThreadPool<T>& other)=delete;//...
public:static ThreadPool<T>* getThreadPool(int num=g_thread_num){if(nullptr==thread_ptr){thread_ptr=new ThreadPool<T>(num);}return thread_ptr;}
private:std::vector<Thread*> threads_;int num_;std::queue<T> task_queue_;pthread_mutex_t lock;//保护临界区(任务队列)的一把锁pthread_cond_t cond;static ThreadPool<T>* thread_ptr;//静态成员 在类外初始化
};
template<class T>
ThreadPool<T>* ThreadPool<T>::thread_ptr =nullptr;

 问题:

由于我们这个项目是线程池,有没有可能多个线程同时访问getThreadPool函数,都进入到了thread_ptr与nullptr这一步判断?

会的,这里是线程不安全的,我们需要加锁以保证安全。

1.1.2 懒汉方式实现线程安全的单例模式

在静态成员函数中加锁,所以该锁也需要是静态的成员变量。

template<class T>
class ThreadPool
{//...
private:ThreadPool(){}
public:static ThreadPool<T>* getThreadPool(int num=g_thread_num){if(nullptr==thread_ptr){//lockGuard lockguard(&mutex); 锁的封装 详细见上篇博文pthread_mutex_lock(&mutex);if(nullptr==thread_ptr){thread_ptr=new ThreadPool<T>(num);}pthread_mutex_unlock(&mutex);}return thread_ptr;}
private:std::vector<Thread*> threads_;int num_;std::queue<T> task_queue_;pthread_mutex_t lock;//保护临界区(任务队列)的一把锁pthread_cond_t cond;static ThreadPool<T>* thread_ptr;//静态成员 在类外初始化static pthread_mutex_t mutex ;//静态成员 在类外初始化
};
template<class T>
ThreadPool<T>* ThreadPool<T>::thread_ptr =nullptr;
template<class T>
pthread_mutex_t ThredPool<T>::mutex=PTHREAD_MUTEX_INITIALIZER;//静态的锁不需要手动释放

 问题:

为什么要判断两次呢?

其他线程想要调用该函数时,如果没有最外层的判断就一定会进行申请锁、释放锁的操作。这样写可以有效减少未来必定要加锁检测的问题

2.其他常见的各种锁

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

我们之前学的锁都是悲观锁。

自旋锁:本质就是通过不断的检测状态(一直打电话催促),来进行资源是否就绪的方案。

(互斥锁:只检测一次,临界资源没就绪就直接挂起。)

什么时候使用自旋锁?临界资源就绪的时间短时(是一个相对的概念,取决于场景的容忍度)。

自旋锁的使用

#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t* lock,int pshared);
int pthread_spin_destroy(pthread_spinlock_t* lock);

无论是挂起等待还是自旋本身都是pthread库帮我们做的,我们只需要使用接口。

#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t* lock);
int pthread_spin_trylock(pthread_spinlock_t* lock); //自旋一次 不成功就返回

 3.读者写者 与消费者生产者的区别

读写锁
公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。

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

区别:

消费者会取走数据,所以消费者消费者之间是互斥关系。二读者写者是共享关系。

        

http://www.lryc.cn/news/6153.html

相关文章:

  • 图文解说S参数(进阶篇)
  • Sentinel源码阅读
  • 2023年浙江食品安全管理员考试真题题库及答案
  • Webstorm 代码没有提示,uniapp 标签报错
  • MySQL-Innodb引擎事务原理
  • Linux操作系统学习(了解环境变量)
  • 数据分析思维(六)|循环/闭环思维
  • C++:类和对象(下)
  • ASP.NET Core MVC 项目 AOP之IResultFilter和IAsyncResultFilter
  • jstack排查cpu占用高[复习]
  • 网络安全-Pyhton环境搭建
  • SpringBoot Mybatis 分页实战
  • 计算机断层扫描结肠镜和全自动骨密度仪在一次检查中的可行性
  • Java多级缓存是为了解决什么的?
  • MongoDB--》索引的了解及具体操作
  • Python open()函数详解:打开指定文件
  • CentOS Stream 9尝鲜安装教程
  • Ambire AdEx 2023 年路线图
  • 两种特征提取方法与深度学习方法对比的小型金属物体分类分析研究
  • 传奇私服搭建网站的几种方法
  • i.MX8MP平台开发分享(clock篇)- 各类clock的注册
  • java ssm计算机系统在线考试平台idea
  • C语言(字符串函数)
  • Maxwell工作流程详解
  • 13- EM算法与GMM高斯混合 (聚类算法) (算法)
  • 【新】华为OD机试 - 二叉树层次遍历(Python)| 刷完获取OD招聘渠道
  • 工作记录------@Accessors(chain = true)引起的BUG,Excel导入时获取不到值
  • JavaEE-HTTP协议(二)
  • 代理的基本原理和多线程的基本原理
  • T38,数的递归