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

Linux——线程(3)线程同步

一、线程同步的引入

通过上面的抢票系统我们发现,有的线程,进行工作(挂锁),当其马上结束工作(解锁),发现外面有很多线程在排队等着加锁执行任务,这个线程解锁后就立马给自己加锁(可以想象成自己能直到解锁的时间,所以加锁的优势比其他线程大),但是如果这个线程本身并没有做什么工作,就会使工作效率低下,线程饥饿等问题。

为了解决这个问题,我们规定:所有线程等待加锁必须排队,加锁的线程解锁后必须排在队尾等待下一次加锁,这样就能保证所有线程都可以执行任务。这就是线程同步。

二、条件变量

条件变量与互斥锁的相关接口几乎完全相同,在这里就不多介绍语法了。

但也有些新的接口。

我们用一个场景来介绍一下这些接口是怎么用的。

我们现在有一个主线程和若干个新线程(共用一个锁)

主线程负责派发任务,新线程负责执行(如果不派发就无法执行)

所以,当一个线程拿到锁进行查看时,如果发现有任务就执行,但如果发现任务还没派发,就会去某个条件变量那里进行等待(pthread_cond_wait接口),后面的线程也是如此,这里是在条件变量这的等待队列(先进先出)。在等待过程中就不会一直的申请加锁而阻止主线程派发任务了。在所有新线程等待的情况下,当主线程加锁后派发任务后,会通知(唤醒)第一个开始等待的新线程,让它去加锁执行任务(pthread_cond_signal接口),同时,主线程也可以一次唤醒多个线程,让他们去抢夺该资源(pthread_cond_broadcast接口),这便是这些接口的使用方法。

三、生产者消费者模型

对于多线程并发的情况,我们可以用生产者消费者模型来解释一下。

首先,我们把主线程称为生产者,可以视为派发任务方,然后多个新线程称为消费者,视为取得任务并执行任务。在二者之间,有一个桥梁——共享或临界资源。那么生产者就会对这份资源进行派发任务的行为,其他消费者要执行任务时,并不会直接向生产者询问索取,而是直接访问该临界资源、上锁并执行。通过这个临界资源,我们就可以讲生产和消费进行解耦了。同时,临界资源可以作为媒介,当生产者派发任务多时可以间接通知消费者并增加消费者数量,反之则减少。

而在这个模型中,我们需要研究多个生产与多个消费的同步互斥关系。

首先,生产者与生产者之间是互斥的,毕竟从现实角度考虑,我派发了就不允许你派发。

消费者与消费者之间也是互斥的,如果任务很少而线程很多时,就会出现我抢到任务了而别人都没抢到。

还有就是生产者与消费者,他们是既互斥又同步的,互斥在于,生产者在派发任务时不允许消费者进来枪任务干扰生产者,而消费者在抢任务时也不许生产者发任务干扰。同步在于,生产者派发任务时,虽然无法抢,但消费者们可以进行排队等待,而对于生产者们也是一样的。

总结就是:2个角色,3个关系,1个交易场所。

有了这个模型,我们就可以把条件变量的接口来解释了。

首先

wait就相当于创建该媒介,让线程在该阻塞队列中等待。signal就是叫醒排在最前面的一个线程执行,broadcast就是一次唤醒所有在队列的线程。

至于wait的接口为什么还要把锁传进去呢?——在线程进入等待的时候要解锁!即解锁与等待是原子性操作。

条件等待是线程间同步的⼀种手段,如果只有⼀个线程,条件不满足,⼀直等下去都不会满足, 所以必须要有⼀个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好 的通知等待在条件变量上的线程。条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。由于解锁和等待不是原子操作。调用解锁之后, pthread_cond_wait 之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么 pthread_cond_wait 将错过这个信 号,可能会导致线程永远阻塞在这个 pthread_cond_wait 。所以解锁和等待必须是⼀个原子操作。

同时,被唤醒的线程也会重新申请锁。

四、信号量

什么是信号量呢?首先,信号量和信号并没有关系。

对于一块公共资源(临界区),我们可以当作一整份使用,比如我们的阻塞队列。但有时,我们也可以分成多块,然后以块为单位访问这块资源,就可以让多个执行流并发访问该块资源了。

首先,信号量是一个计数器,用来表明临界资源中的资源数目。而我们申请信号量,本质是对临界资源的预订(并非等同于使用),只要预订成功即使不使用,其他线程也无法使用。而我们信号量作为保护公共资源的机制,本身自己也属于临界资源,因此申请信号量的操作必须是原子的。此外,如果我们的计数器只有0和1,那不就成为了我们在线程互斥提到的锁了吗。

五、有关信号量的环形队列

在上面的生产者消费者模型,我们的交易场所是阻塞队列,有了信号量的引入,我们介绍一种新的交易场所模型——基于信号量的环形阻塞队列(首尾相连)。(为空或为满,都指向一个位置)

在这个队列中,任何人访问此临界资源,必须先申请信号量!对于生产者来说,在意的是剩余空间,而对于消费者来讲在意的是剩余数据。我们介绍一下此队列的两种情况

1.生产消费同时访问同一个位置

如果为空,说明队列无数据,消费者就需要阻塞等待,让生产者放入数据。

如果为满,说明队列满数据,生产者就需要阻塞等待,让消费者取得并拿出数据。这两种情况都可以保证原子性!体现了互斥同步原则。

2.指向不同位置

此时就有点像追及问题,此时生产者和消费者就可以同时进行访问临界资源,直到回到1情况再继续一方等待。这种情况就满足了并发原则。

因此,我们要设计两个信号量分别给生产消费,以让他们看到临界资源的使用情况。

六、信号量的相关接口

1.sem_init

第一个参数不说了,第二个是是否设置为线程间共享,默认设置为0即可,第三个是设置信号量的初始值。

2.sem_destroy

3. P操作(申请信号量)

申请失败就会阻塞

4.V操作(释放信号量)

提一下,假设我们现在是生产者的视角,我们就需要让空间信号量先进行P操作,等放入数据后,再让消费者(数据信号量)进行V操作。

关于P、V操作

P、V本质都是对信号量的操作,P操作就是申请信号量,预订资源(但如果信号量为负就会阻塞等待,直到有其他资源释放导致该信号量++,然后把信号量--),V操作是释放资源以及唤醒其他阻塞等待的线程,如果没有等待的线程,那么信号量的值会累加。

对于空间信号量(生产)和数据信号量(消费)而言,我们简述一下大致流程:
当要放入数据时,空间信号量会预订(P)(如果没有等待就进行插入操作),然后放完数据后,让数据信号量进行V(因为插入一个数据后导致数据个数增加,消费者对于这块空间的可访问量++)。而消费者进行消费过程正好与之相反。 (我们把信号量看成可访问资源块的数量就好理解了)

相关文章:

  • Elsevier latex报错Paragraph ended before \@citex was complete.<to be read again>
  • Pinia: vue3状态管理
  • 【Android】四大组件
  • Mem0.ai研究团队开发的全新记忆架构系统“Mem0”正式发布
  • 2025年人工智能火爆技术总结
  • 【Linux网络】I/O多路转接技术 - epoll
  • epoll函数
  • 【Shell 脚本编程】详细指南:第四章 - 循环结构(for、while、until) 深度解析
  • 60常用控件_QSpinBox的使用
  • 排序算法——冒泡排序
  • C语言学习之动态内存的管理
  • 交我算使用保姆教程:在计算中心利用singularity容器训练深度学习模型
  • caffe适配cudnn9.6.0(ai修改代码踩坑)
  • synchronized与Lock深度对比
  • 随机森林实战:从原理到垃圾邮件分类
  • Windows下Python3脚本传到Linux下./example.py执行失败
  • AdaBoost算法详解:原理、实现与应用指南
  • 极简GIT使用
  • 补4月30日
  • 常见电源的解释说明
  • 几天洗一次头发最好?终于有答案了...
  • 韩国经济副总理崔相穆宣布辞职
  • 解放日报:抢占科技制高点,赋能新质生产力
  • 国铁集团去年收入12830亿元增3%,全年铁路运输利润总额创新高
  • 美参议院通过新任美国驻华大使任命,外交部回应
  • 辽宁辽阳市白塔区一饭店发生火灾,当地已启动应急响应机制