【linux】线程 (三)

 

 13. 常见锁概念

(一)了解死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程占有的,且不释放的资源,而处于的一种永久等待状态

(二)死锁四个必要条件

  • 互斥条件:一个资源每次只能被一个执行流使用
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

(二)避免死锁

  • 破坏死锁的四个必要条件
  • 加锁顺序一致 (不是交错申请不同的锁资源)
  • 避免锁未释放的场景
  • 资源一次性分配

14. linux线程同步

(一)条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了,为了使抢夺锁资源更公正(防止饥饿问题),就需要把所有等待锁资源的线程放入一个等待队列,直到线程被唤醒

。这种情况就需要用到条件变量

注意:

条件变量必须依赖锁

(二)同步概念

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步

(三)条件变量相关函数

条件变量函数 初始化

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict

attr);

参数:

cond:要初始化的条件变量

attr:条件变量的属性,一般设置成NULL

销毁

int pthread_cond_destroy(pthread_cond_t *cond)

参数:要销毁的条件变量函数

等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

参数:

cond:要在这个条件变量上等待

mutex:互斥量

注意:

参数中有互斥量,是因为实现同步与互斥,需要先加锁,再判断(判断也是访问临界资源,和加锁保护线程安全对应)是否进入等待队列休眠,而这个函数里面有互斥量是为了把锁资源释放,接下来访问临界资源的顺序是按照队列里面的顺序进行的(前提是线程被唤醒)

大概代码思路

pthread_mutex_lock(&mutex); // 加锁

pthread_cond_wait(&cond); //放入等待队列

pthread_mutex_unlock(&mutex); //解锁

注意:

  1. 唤醒可以由其它线程去唤醒,如主线程
  2. 三个函数的顺序不要更换(解锁和等待都不是原子操作)

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);

唤醒队列中的所有线程

int pthread_cond_signal(pthread_cond_t *cond);

按顺序唤醒队列中的一个线程

注意:

  1. 唤醒之后的线程需要申请锁资源
  2. 唤醒的时候,可能出现唤醒错误的情况。比如在生产者消费者模型中(多生产者多消费者容易出现这种错误),唤醒了多个生成者,并且消费者此时也是属于唤醒状态,导致生产者和生产者之间,消费者和生产者之间竞争同一把锁,锁最后的分配,可能导致临界资源访问条件不满足(唤醒之后拿到锁资源的线程,有可能这个时候的访问条件不满足,但依然会执行后面的代码),出现错误

正确代码思路

pthread_mutex_lock(&mutex); // 加锁

while(... ...) //判断访问资源的条件是否成立

{

pthread_cond_wait(&cond); //放入等待队列

}

pthread_mutex_unlock(&mutex); //解锁

注意:

判断用while,是为了保证,线程被唤醒(出了等待队列),并且竞争到了锁资源后,再一次判断资源条件是否成立,才能决定后面的代码能否执行

15. 生产者消费者模型

(一)使用生产者消费者模型的原因

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯

生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列;消费者不找生产者要数据,而是直接从阻塞队列里取

阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力,这个阻塞队列就是用来给生产者和消费者解耦的

(二)生产者消费者模型优点

  • 解耦
  • 支持并发
  • 支持忙闲不均

(三)基于BlockingQueue的生产者消费者模型

BlockingQueue

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出

(四)对生产者消费者模型的总结

1. 有三种关系

  • 生成者和生成者之间的关系:互斥
  • 生产者和消费者之间的关系:互斥,同步
  • 消费者和消费者之间的关系:互斥

2. 有两者角色

生产者和消费者

3. 有一种交易场所

特定结构的内存空间

16. POSIX信号量

POSIX信号量和 SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步

17. 基于环形队列的生产消费模型

(一)了解基于环形队列的生产消费模型

环形队列采用数组模拟,用模运算来模拟环状特性

判断队列是否为空或者为满,我们交给信号量去处理

注意:

  1. 信号量的本质是一个计数器,用来描述临界资源的数目(这里不把临界资源当成一个整体,而是很多份,多个线程并发访问临界资源中的不同份)
  2. 临界资源是否就绪的判断是在临界区之外的(和前一个生产消费模型区分开来),在申请信号量的时候,就已经间接在做判断了(这一步是预定操作,即预定成功,则整个临界资源必有一份资源是属于预定成功的线程的)

(二)基于环形队列的生产消费模型的原理

 

18. 线程池

(一)了解线程池

线程池:

一种线程使用模式

线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程(提前开辟好的),等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度

池化计数:

线程会用到池化技术(以空间换时间),大致操作(C++为例):现在C++类内创建线程(创建的线程当作类内成员变量),再利用原生线程去实现生产者消费者模型

注意:

  1. 在类内创建时,若线程执行的函数也是类内函数,会出现错误(因为执行的函数传参只有一个void*,而类内函数会多一个this指针),所以该执行函数必须是静态的
  2. 若执行函数是静态的,同样会出现一个问题,即静态成员函数无法访问非静态成员变量(没有this指针),所以执行函数的参数void*必须是this指针,通过穿过来的this指针,访问非静态成员变量

(二)线程池的应用场景

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。因为单个任务小,而任务数量巨大, 但对于长时间的任务,线程池的优点就不明显了
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限

(三)线程池示例

  1. 创建固定数量线程池,循环从任务队列中获取任务对象
  2. 获取到任务对象后,执行任务对象中的任务接口

19. 线程安全的单例模式

(一)什么是单例模式

单例模式是一种 "经典的, 常用的, 常考的" 设计模式(这个类的对象,只允许创建一个)

(二)什么是设计模式

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

(三)单例模式的特点

  1. 某些类, 只应该具有一个对象(实例), 就称之为单例
  2. 在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据

(四)饿汉实现方式和懒汉实现方式

举例类比(洗完的例子)

饿汉方式:

吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭

懒汉方式:

吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式

注意:

懒汉方式最核心的思想是 "延时加载". 从而能够优化服务器的启动速度(在准备进程启动时,需要把一些数据加载到内存,这一个过程的时间少)

饿汉方式 代码实现

template <typename T>
class Singleton 
{
static T data;
public:
static T* GetInstance() 
{
return &data;
}
};

data 是静态类内成员,会在进程启动之前,早就创建好,而不是等到启动进程时,再去创建

 

懒汉模式 代码实现

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

inst指针是要等待使用的时候,所指向的内容才会在堆上开辟新空间

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

代码

static phread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
template <typename T>
class Singleton 
{
static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.
public:
static T* GetInstance() 
{
if (inst == NULL) //这一次的判断可以提高效率,使得没有进入第二个判断的线程,直接执行非临界资源的代码
{ 
lock.lock(); // 使用互斥锁, 防止多个线程进入第二个判断,保证多线程情况下也只调用一次 new
if (inst == NULL) 
{
inst = new T();
}
lock.unlock();
}return inst;
}
};T* Singleton<T>:: inst = nullptr;

注意:(代码不是完整的)

lock(),unlock()底层都是用原生线程库封装了

20. STL , 智能指针和线程安全

(一)常见问题

  • STL中的容器是否是线程安全

不安全

原因:

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

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

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

  • 智能指针是否是线程安全
  1. 对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.
  2. 对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.

(二)其他常见的各种锁

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

21. 读者写者问题

读写锁

在编写多线程的时候,有一种情况是十分常见的:有些公共数据修改的机会比较少,相比较改写,它们读的机会反而高的多。

读写锁可以专门处理这种多读少写的情况

注意:写和写竞争,写和读竞争且同步,读和读共享资源,读锁优先级高

相关函数

初始化

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

代码(部分)

void * reader(void * arg)  //读端
{
char *id = (char *)arg;
while (1) 
{
pthread_rwlock_rdlock(&rwlock);
if () //判断
{
pthread_rwlock_unlock(&rwlock);
break;
}
pthread_rwlock_unlock(&rwlock);
}
return nullptr;
}void * writer(void * arg) //写端
{
char *id = (char *)arg;
while (1) 
{
pthread_rwlock_wrlock(&rwlock);
if () //判断条件
{
pthread_rwlock_unlock(&rwlock); 
break;
}}
return nullptr;
}

中间一些过程省略了

底层实现(伪代码)

int reader_count = 0;
mutex_t rlock,wlock;
读端:
lock(&rlock)
reader_count++;
if(reader_count == 1) //第一个竞争到所资源的读端
{lock(&wlock); //写段不能访问
}
unlock(&rlock)lock(&rlock);
reader_count--;
if(reader_count == 0)//一开始一批读端的最后一个
{unlock(&wlock); //此时读端可以竞争
}
unlock(&rlock);写端:
lock(&wlock);
// ... 写入
unlock(&wlock);

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

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

相关文章

uniapp项目结构基本了解

基本结构的解释 App.vue&#xff1a;应用的根组件&#xff0c;定义全局布局和逻辑。pages/&#xff1a;存放各个页面的 .vue 文件&#xff0c;定义应用的具体页面和功能模块。main.js&#xff1a;应用入口文件&#xff0c;初始化应用&#xff0c;挂载 App.vue。manifest.json&…

【C++】— 一篇文章让你认识STL

文章目录 &#x1f335;1.什么是STL&#xff1f;&#x1f335;2.STL的版本&#x1f335;3.STL的六大组件&#x1f335;4.STL的重要性&#x1f335;5. 如何学习STL&#x1f335;6. 学习STL的三种境界 &#x1f335;1.什么是STL&#xff1f; STL是Standard Template Library的简称…

『完整代码』靠近显示对话图标

在NPC预制体中增加Canvas 并设置 创建Image 并设置 隐藏Image 在场景中创建Canvas 重命名为CurrentCanvas 创建空物体设置底端锚点 重命名为DownPin 创建Image重命名为TalkUI 选择图片设置 创建Image并设置 重命名为imgNpc 创建文本并设置 重命名为txtNpc 可以给图片与文本加一…

centos 安装达梦数据库

一、环境准备 1.1、确认操作系统的版本和数据库的版本是否一致 ## 查看系统版本&#xff1a;cat /etc/redhat-release CentOS Linux release 7.5.1804 (Core)1.2、关闭防火墙和Selinux # 查看selinux是不是disabled / enforce cat /etc/selinux/config## 查看防火墙状态 fir…

windows mysql 8.0版本重置root密码

1.停止mysql服务 以管理员运行cmd 2.安全模式启动 mysqld --console --skip-grant-tables --shared-memory 3.修改密码 再开个cmd窗口就可以进入了&#xff1a;mysql 先进入mysql database&#xff1a;use mysql 修改密码&#xff1a;ALTER USER rootlocalhost IDENTIFIED …

使用 InfiniBand 写入带宽对 NVIDIA GPUDirect RDMA 进行基准测试

简介 性能基准测试是 HPC 的标志。最现代的超级计算机是具有异构架构的计算节点集群。在这样的节点中&#xff0c;我们可以看到经典 CPU 和专用计算协处理器 (GPU)。本教程介绍了使用基于 InfiniBand 写入带宽 (ib_write_bw) 构建的定制脚本对 NVIDIA GPUDirect 远程直接内存访…

Xmind一款极简思维导图和头脑风暴软件,支持PC和移动端,Xmind 2024.10.01101版本如何升级到Pro版?简单操作,最新可用!

文章目录 Xmind下载安装Xmind免费升级到Pro Xmind 是一款全功能的思维导图和头脑风暴软件&#xff0c;不限制节点和文件数&#xff0c;创新无限&#xff0c;界面纯净简洁无广告&#xff0c;支持PC和移动端&#xff0c;思维导图和大纲视图自由切换&#xff0c;可本地化文档存储&…

AutoFixture:.NET 的假数据生成工具

上次推荐过《Bogus&#xff1a;.NET的假数据生成利器》方便我们制造假数据测试。今天继续推荐另外一个也是非常流行的工具。 01 项目简介 AutoFixture 是一个用于 .NET 的测试工具&#xff0c;它允许开发者在单元测试中自动生成随机的测试数据。它支持广泛的数据类型&#xf…

如何使用DockerSpy检测你的Docker镜像是否安全

关于DockerSpy DockerSpy是一款针对Docker镜像的敏感信息检测与安全审计工具&#xff0c;该工具可以帮助广大研究人员在Docker Hub上检测和搜索自己镜像的安全问题&#xff0c;并识别潜在的泄漏内容&#xff0c;例如身份验证密钥等敏感信息。 功能介绍 1、安全审计&#xff1a…

React源码03 - React 中的更新

03 - React 中的更新 React 中创建更新的方式&#xff1a; 初次渲染&#xff1a;ReactDOM.render、ReactDOM.hydrate 后续更新&#xff1a;setState、forceUpdate 1. ReactDOM.render() 先创建 ReactRoot 顶点对象然后创建 FiberRoot 和 RootFiber创建更新&#xff0c;使应用进…

ArcGIS应用指南:多尺度渔网创建

在GIS中&#xff0c;创建渔网矢量文件是GIS中的一项常见任务&#xff0c;通过将研究区域划分为规则的网格&#xff0c;可以更精细地分析和管理城市空间数据。本文以厦门市行政区为例&#xff0c;详细介绍了如何创建不同尺度的渔网矢量网格&#xff0c;以适应不同区域的发展特点…

DCS项目调试踩坑记录

最近在调试一个DCS项目&#xff08;集散控制系统&#xff09;&#xff0c;实际上就是一个新建厂区的控制系统。PLC用的是西门子1500&#xff0c;控制画面使用组态王7.5。 在调试过程中&#xff0c;发现给西门子DB块的变量转移到组态王太难了&#xff0c;因此记录一下&#xff0…

RHCE【远程连接服务器】

目录 一、远程连接服务器简介 二、加密技术简介 SSH工作过程&#xff1a; &#xff08;1&#xff09;版本协商阶段 &#xff08;2&#xff09;密钥和算法协商阶段 &#xff08;3&#xff09;认证阶段 &#xff08;4&#xff09;会话请求阶段 &#xff08;5&#xff0…

互联网人口红利趋缓下的社群粉丝经济新模式探索

摘要&#xff1a;随着互联网人口红利消失近十年&#xff0c;国内互联网人口红利爆发时期凭借大量用户取得成功的模式不再适用。如今互联网人口增长进入平缓期&#xff0c;社群粉丝经济成为新方向。其能借助人群画像精准推送营销信息&#xff0c;降低成本。如“21 链动模式 AI 智…

android openGL ES详解——混合

一、混合概念 混合是一种常用的技巧&#xff0c;通常可以用来实现半透明。但其实它也是十分灵活的&#xff0c;你可以通过不同的设置得到不同的混合结果&#xff0c;产生一些有趣或者奇怪的图象。混合是什么呢&#xff1f;混合就是把两种颜色混在一起。具体一点&#xff0c;就…

【前端】如何制作一个自己的网页(16)

上次&#xff0c;我们学习了两种复合CSS选择器&#xff0c;以及两种内容分组的方式&#xff1a;整体布局的div元素和局部布局的span元素。 学习目标 学习另一种对内容分组的方式&#xff1a;列表结构。首先&#xff0c;我们会简单了解下什么是HTML的列表结构。然后&#xff0…

《深度学习》YOLO v1网络架构 、损失值、NMS极大值抑制

目录 一、Yolo系列v1 1、核心思想 2、示例 3、流程图解析 二、YOLO系列v1损失函数 1、位置误差 2、置信度误差 3、类别概率损失 三、NMS非极大值抑制 1、概念 2、步骤 四、YOLO v1优缺点 1、优点 1&#xff09;速度快 2&#xff09;端到端 3&#xff09;多尺度…

Python 学习笔记(十二)—— 网络编程

目录 一、网络编程的基本概念 1.1 IP地址 1.1.1 IP的版本 1.1.2 IP的分类 1.1.2.1 公有地址 1.1.2.2 私有地址 1.1.3 IP地址的范围 1.1.4 回环测试 1.2 常见的网络设备 1.3 端口 1.3.1 端口分配 二、网络通信协议 2.1 常用网络协议 2.2 OSI网络协议七层模型 2.3…

Java访问修饰符private,default,protected,public

在Java中&#xff0c;访问修饰符决定了类、方法、变量和构造器的可见性和可访问范围。这里有四个主要的访问修饰符&#xff1a;private、default&#xff08;未显式指定&#xff09;、protected 和 public。下面分别解释它们的作用&#xff1a; 1.private 作用&#xff1a;使…

【C++指南】类和对象(四):类的默认成员函数——全面剖析 : 拷贝构造函数

引言 拷贝构造函数是C中一个重要的特性&#xff0c;它允许一个对象通过另一个已创建好的同类型对象来初始化。 了解拷贝构造函数的概念、作用、特点、规则、默认行为以及如何自定义实现&#xff0c;对于编写健壮和高效的C程序至关重要。 C类和对象系列文章&#xff0c;可点击下…