一.回顾
在这之前已经讲解了System V版本的信号量,主要内容为以下3点:
- 信号量本质是一把计数器
- 申请信号量本质就是预订资源
- PV操作(申请和释放)是原子的
今天我们要学习的是POSIX版本的信号量,以上三点同样遵循
二.信号量VS互斥锁
1.联系:
信号量和互斥锁都是保护公共资源的手段,想要通过临界区访问临界资源,必须先要申请信号量或者互斥锁。并且计数器为1的信号量功能等同于互斥锁。
2.区别:
- 互斥锁将公共资源当成一个整体使用,信号量可以实现把公共资源分成若干区域,给不同的执行流同时使用。
- 申请锁成功,线程进来后一般还要使用如if这样的判断语句,来检测资源是否就绪,如果没有就绪,为了并发的高效,还需要等待条件变量。(进电影院之后再现场买票)。但是申请信号量成功,线程进来之后无需判断资源是否就绪,因为只要申请信号量成功,说明一定有资源分配给你,并且这份资源只有你自己能访问。(提前买电影票)
- 互斥锁在信号量都在pthread库中,不过互斥锁声明在<pthread.h>头文件,信号量声明在<semaphore.h>中
线程使用信号量流程:
申请信号量->访问指定区域->释放信号量
三.信号量用法
1.初始化信号量
sem是定义的信号量。pshared指定信号量是否进程共享,为0则线程共享,非0则进程共享。value则是计数器的初始值,表示想把公共资源分成多少份使用
2.申请信号量(P操作)
3.释放信号量(V操作)
4.销毁信号量
四.基于环形队列的生产者消费者模型
1.生产消费过程
生产者和消费者在环形队列的生产消费过程:
- 生产者不能把消费者套一个圈
- 消费者不能超过生产者
- 生产者和消费者只有两种场景会指向同一个位置:队列为空和为满
只要生产者和消费者不指向同一个位置,就可以多线程并发访问环形队列。如果为空,只能让生产者跑,如果为空,只能让消费者先跑。“只能”表现出的是互斥,“先”表现出的是同步。所以基于环形队列的生产者消费者模型,也有同步和互斥,但这种同步和互斥是局部性的,只有队列为空为满时才需要维持,这样就实现了生产和消费的并发。
2.信号量设计
生产者认为空间是资源,消费者认为数据是资源,所以分别为生产者和消费者设计条件变量,来表示空间和数据的数量。
初始时,生产者的信号量是n,消费者的信号量是0。
伪代码:
//1.初始化
p->sem_space = N;
c->sem_data = 0;//2.生产者
P(sem_space)
//生产行为,位置待定
V(sem_data)
//3.消费者
P(sem_data)
//消费行为,位置待定
V(sem_space)
3.多生产和多消费
以上的两个信号量可以保证生产者和消费者之间的同步和互斥。如果是多个生产者和消费者,信号量能否保证生产者之间,消费者之间的互斥关系呢?
不能!因为生产者共用同一个_p_step,消费者共用同一个_c_step,读写位置的安全无法保证,也就不能保证多线程访问的是不同的位置,所以需要设计两个锁,分别对_p_step和_c_step进行保护。