Linux第十三讲:线程同步和互斥
- 1.线程互斥
- 1.1进程线程间的互斥背景概念
- 1.2什么是锁
- 1.2.1认识锁,理解锁
- 2.线程同步
- 2.1条件变量
- 2.2生产和消费模型
- 2.3基于阻塞队列(blockqueue)的生产消费模型
- 2.3.1单生产,单消费的阻塞队列模拟实现
- 2.3.2多生产,多消费的阻塞队列模拟实现
- 2.4生产消费模型好处补充
- 3.POSIX信号量
- 3.1什么是POSIX信号量
- 3.2基于唤醒队列的生产消费模型概念
- 3.3信号量接口的认识和信号量的封装
- 3.4基于环形队列的生产消费模型的模拟实现
- 3.4.1单生产,单消费
- 3.4.2多生产,多消费
- 4.日志与线程池
- 4.1日志
- 4.1.1日志的基本概念
- 4.1.2日志的实现
- 4.2线程池
- 4.2.1线程池的实现
- 4.2.1.1普通版本的线程池实现
- 4.2.1.2单例模式 && 饿汉 && 懒汉方式
- 4.2.1.3单例式线程池的实现
- 5.线程安全与重入问题
- 6.死锁问题
- 6.1什么是死锁
- 6.2死锁的必要条件
- 6.3避免死锁
- 7.STL、智能指针和线程安全问题
1.线程互斥
1.1进程线程间的互斥背景概念
1.2什么是锁
当多个线程并发操作共享变量时,容易带来一些问题:
为什么会引发这种问题呢?:
1.2.1认识锁,理解锁
通过上面的问题,我们引入锁的概念:
锁的使用如下:
感性理解锁:
锁的原理:
C++也提供了锁的接口,可以自行查看
2.线程同步
同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
我们先感性理解线程同步:
2.1条件变量
感性理解条件变量:
了解了条件变量,那么条件变量该如何使用呢?:
我们通过代码来展示条件变量实现的线程同步操作:
2.2生产和消费模型
2.3基于阻塞队列(blockqueue)的生产消费模型
什么是阻塞队列:
2.3.1单生产,单消费的阻塞队列模拟实现
完整代码实现,上面没有debug,下面的debug了:
2.3.2多生产,多消费的阻塞队列模拟实现
多生产,多消费的代码和单生产,单消费的相同,因为无论是多少生产者或消费者,都只有一个线程能够进入临界区域!
2.4生产消费模型好处补充
3.POSIX信号量
和System V类似,POSIX只是一种标准。
3.1什么是POSIX信号量
3.2基于唤醒队列的生产消费模型概念
3.3信号量接口的认识和信号量的封装
3.4基于环形队列的生产消费模型的模拟实现
3.4.1单生产,单消费
3.4.2多生产,多消费
多生产,多消费的模拟实现就和单生产,单消费的模拟实现有所不同。
因为单生产消费只需要维护生产者和消费者之间的互斥和同步关系,使用信号量完全可以实现。
而多生产消费还需要维护生产者之间的互斥关系,以及消费者之间的互斥关系,所以这里需要两把锁来维护:
进一步理解信号量:
1.信号量把对临界资源是否存在,是否就绪,以原子性的形式,在需要访问临界资源之前就做出判断了
2.如果资源可以拆分,比如数组,那么可以考虑使用sem
如果资源不可拆分,比如阻塞队列,就使用mutex
4.日志与线程池
什么是线程池?:
然而线程池每天的运行结果我们想要实时查看,就需要用到日志记录
4.1日志
4.1.1日志的基本概念
那么这样的日志该如何实现呢?:
4.1.2日志的实现
完整代码如下:
4.2线程池
4.2.1线程池的实现
4.2.1.1普通版本的线程池实现
全部代码如下:
4.2.1.2单例模式 && 饿汉 && 懒汉方式
饿汉方式和懒汉方式的不同:
我们将使用懒汉方式实现线程池,而懒汉方式被使用的次数更多。
因为我们并不能确定什么时候需要使用到类对象,如果类对象很大,提前被创建,那么在不使用的这段时间内,类对象占用的空间也不能被其它对象使用,会造成资源的浪费。
操作系统也有很多懒汉方式的应用:
当我们malloc时,并不会直接申请物理内存,而是先申请虚拟内存。当我们需要使用物理内存时,才通过缺页中断的方式,申请物理内存。
4.2.1.3单例式线程池的实现
那么我们如何保证只创建一个对象呢?:
5.线程安全与重入问题
6.死锁问题
6.1什么是死锁
6.2死锁的必要条件
知道什么是死锁,那么就需要解决死锁。只有知道了死锁的生成条件,那么破坏其中任意一个条件,就会让死锁问题解决:
6.3避免死锁
知道了死锁的必要条件,破坏任意一个条件就可以避免死锁:
7.STL、智能指针和线程安全问题
1.STL中的容器不是线程安全的,因为STL的设计初衷就是将性能挖掘到极致,如果加锁就必然会破坏性能问题。而且对于不同的容器,加锁的方式也会不同
2.智能指针是否是线程安全的?:1/对于unique_ptr,由于只是在当前代码块范围内生效,所以不涉及线程安全问题 2/对于shared_ptr,使用了引用计数,但是标准库实现时考虑了这个问题,将引用计数设置为原子操作
3.也有很多其他的锁:悲观锁和乐观锁(了解即可):