王道计算机操作系统学习笔记心得
1.生产者,消费者模型
生产者消费者模型概述:
有一个缓冲区,生产者线程向缓冲区中填入数据,消费者线程从缓冲区中读取出一个数据。
需要注意:
- 只有当缓冲区有数据时,消费者线程才可以取出缓冲区中的数据。否则阻塞等待
- 只有缓冲区有空间时,生产者线程才可以向缓冲区生产数据。否则阻塞等待
- 缓冲区属于临界资源,线程访问时需要互斥访问。
根据上面三种注意事项和信号量的PV操作可以分析出:
-
缓冲区还有数据V======>P消费者线程消费
-
生产者线程(进程)生产P<========V缓冲区还有控件
-
临界资源互斥访问,可以使用互斥信号量来实现。或者加锁实现。
伪代码如下:
semaphore mutex = 1;
//互斥信号量,实现对缓冲区的互斥访问
semaphore empty = n;
//同步信号量,表示空闲缓冲区的数量,n代表缓冲区的大小
semaphore full = 0;
//同步信号量,表示产品的数量,也即非空缓冲区的数量
//生产者
producer () {
while(true){
生产一个缓冲区数据;
P(empyt)//空闲信号量-1
//互斥访问临界区
P(mutex)
把产品放入缓冲区;
V(mutex)
V(full)//代表数据的信号量+1
}
}
//消费者
consumer () {
while(true){
P(full);//数据信号量因为取出了要-1
//互斥访问临界区
P(mutex)
从缓冲区取出一个数据;
V(mutex)
V(empty)//缓冲区空闲增加,empyt信号量+1
使用缓冲区数据;
}
}
实例:Linux环境下C++实现环形生产者消费者模型,这里互斥操作使用互斥锁实现
#pragma once
#include<iostream>
#include<vector>
#include<semaphore.h>
#include<pthread.h>
#define SIZE 32
template<class T>
class RingQueue
{
private:
std::vector<T>q;
int Cap;
int b_pos;//只有生产者线程会访问,单生产者不需要保护这个变量,下面也是如此
int d_pos;
sem_t block_Num;
sem_t data_Num;
private:
void P(sem_t&s)
{
sem_wait(&s);
}
void V(sem_t&s)
{
sem_post(&s);
}
public:
RingQueue(int cap=SIZE):Cap(cap),b_pos(0),d_pos(0)
{
q.resize(Cap);
sem_init(&block_Num,0,Cap);//开始空间信号量为Cap
sem_init(&data_Num,0,0);//开始数据信号量为0
}
void Push(const T&in)//生产数据
{
P(block_Num);//关心空间信号量,申请空间信号量,P操作使空间信号量-1
q[b_pos]=in;
V(data_Num);//生产者生产了一个数据,数据信号量,V操作使数据信号量+1
b_pos++;
b_pos%=Cap;
}
void Pop(T&out)
{
P(data_Num);//当数据信号量为0时,消费者线程挂起等待,所以一定是生产者线程先运行,消费者线程后运行
out=q[d_pos];
V(block_Num);//消费了数据,所以空间信号量V操作使空间信号量+1
d_pos++;
d_pos%=Cap;
}
~RingQueue(){
sem_destroy(&block_Num);
sem_destroy(&data_Num);
}
};
多生产者,多消费者模型
问题描述:
A生产者向缓冲区生产A数据,A数据只能被A消费者消费。
B生产者向缓冲区生产B数据,B数据只能被B消费者消费。
这四个线程(进程)公用一个缓冲区,通过信号量描述上述关系。
首先:分析同步互斥关系
- 缓冲区是临界资源,需要互斥访问。A,B生产者是互斥关系;A,B消费者是互斥关系。
- A生产者生产后,A消费者下可以消费,是同步关系
- 同理B生产者和B消费者也是同步关系
伪代码:
semaphore mutex = 1;
//实现互斥访问缓冲区
semaphore a = 0;
//缓冲区中有几个A资源
semaphore b = 0;
//缓冲区中有几个B资源
semaphore buff = 1;
//缓冲区大小
A(){
while(true){
生产A
//申请缓冲区资源
P(buff)
//互斥访问临界资源
P(mutex)
将A放入缓冲区中
V(mutex)
V(a)//A资源多了一个,信号量a++
}
}
B(){
while(true){
生者B
P(buff)
P(mutex)
将B放入缓冲区中
V(mutex)
V(b)
}
}
delA(){
while(true){
//看缓冲区中是否有自己需要的数据
P(a)
P(mutex)
从缓冲区取出A
V(mutex)
//缓冲区多空出一个资源
V(buff)
处理A任务
}
}
delB(){
while(true){
P(b)
P(mutex)
从缓冲区中取出B
V(mutex)
V(buff)
处理B任务
}
}
需要注意:
因为此题的缓冲区大小为1,所以其实可以不设置互斥信号量mutex也可以实现所有线程互斥访问临界资源。
如果缓冲区大小大于1,则就需要设置互斥信号量mutex实现互斥操作。
其次:在分析同步类问题时,需要抽象成事件的发生会导致那些事件发生来分析,不要从进程(线程)的具体操作会导致什么结果分析。
2. 吸烟者问题
问题描述:
- 假设一个系统有三个抽烟者进程和一个供应者进程。
- 每个抽烟者不停地卷烟并抽掉它,但是要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。
- 三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水。
- 供应者进程无限地提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉完成了,供应者就会放另外两种材料再桌上,这个过程一直重复(让三个抽烟者轮流地抽烟)
首先分析同步互斥关系:
- 桌子可以看作缓冲区,进程(线程)访问需要互斥访问
- 供应者可以看作生产者线程,生产三种组合的数据。
组合一:纸+胶水 组合二:烟草+胶水 组合三:烟草+纸 - 缓冲区是组合一,一号吸烟者运行,同步关系
- 同理三种组合就是三组同步关系,每一组同步关系都需要设置一个信号量
- 吸烟者完成后,发送消息给供应者。是同步关系
因为,缓冲区大小为1,所以不需要这是互斥变量来保证临界资源安全。
伪代码:
semaphore offer1 = 0;
//桌上组合一的数量
semaphore offer2 = 0;
//桌上组合二的数量
semaphore offer3 = 0;
//桌上组合三的数量
semaphore finish = 0;
//抽烟是否完成
int i=0;
//用来实现三个吸烟者轮流吸烟(i=(i+1)%3来实现轮流吸烟)
provider (){
while (1){
if(i==0){
将组合一放桌上;
V(offer1);//通知吸烟者,资源就绪
}
else if(i==1){
将组合二放桌上;
V(offer2);
}
else{
将组合三放桌上;
V(offer3);
}
i=(i+1)%3;
//等待吸烟者吸烟完毕后继续循环
P(finish);
}
}
smoker1 (){
while(1){
P(offer1)
从桌上拿走组合一;
卷烟;抽掉;
V(finish)
}
}
smoker2 (){
while(1){
P(offer2)
从桌上拿走组合二;
卷烟;抽掉;
V(finish)
}
}
smoker3 (){
while(1){
P(offer3)
从桌上拿走组合三;
卷烟;抽掉;
V(finish)
}
}