第4章 进程同步

进程间的关系(2⭐)

进程并发与时间有关的错误

// P1
R1 = count;
R1 = R1 + 1;
count = R1;
// P2
R2 = count;
R2 = R2 + 1;
count = R2;

假设开始时count=1

  • 如果P1、P2顺序执行,那么最终count将被赋值为3

  • 假如系统P1、P2如下顺序执行:

    1. R1 = count //R1值为1
    2. R2 = count //R2值为1
    3. R1 = R1 + 1 //R1值为2
    4. R2 = R2 + 1 //R2值为2
    5. count = R1 //count被赋值为R1=2
    6. count = R2 //count被赋值为R2=2

    即最终结果count=2,与P1、P2顺序执行的结果不一致,即错误。

原因

  1. 共享了变量count
  2. “同时”使用了这个变量count

解决方法

  1. 取消共享(不现实)
  2. 允许共享,不允许同时使用**(可行)进程互斥**

进程间的两种形式的制约关系

间接相互制约关系(互斥关系)

进程间要通过某种中介发生联系,是无意识、无安排的。

用户要使用这类资源之前需要提出申请,不能直接使用。

直接相互制约关系(同步关系)

进程间的相互联系是有意识的、有安排的。

多个进程中发生的事件存在某种时序关系,相互合作完成一项任务

一个进程运行到某一点时要求另一伙伴进程为它提供消息,在未获得消息之前,该进程处于等待/阻塞状态,获得消息后被唤醒进入就绪态。

进程在时间上的先后次序举例(前驱图)
P6
P4
P2
P1
P5
P3

临界资源

系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源/互斥资源,也可以是软件的共享变量。

临界区

在进程中涉及到临界资源的程序段

信号量+P/V操作控制进程(5⭐)

信号量初始值可用资源的数目
信号量仅通过P或V操作来访问
P操作:wait(s)或P(s)
V操作:signal(s)或V(s)
PV操作具有原子性

原子性:指事务的不可分割性,一个事务的所有操作要么不间断地全部被执行,要么一个也没有执行。

整形信号量

将信号量S定义成一个整数

获取资源P操作

以下semaphore与int类型相同

void P(semapore *s) // 或wait
{
    while(s <= 0); // 无操作 do no-op
    s--;
}

释放资源V操作

void V(semapore *s) // 或signal
{
    s++;
}

利用信号量实现进程互斥访问临界资源

semapore s = 1;
Process
{
    P(&s);
    操作C;
    V(&s);
}
Mon 07 12:00 Tue 08 12:00 Wed 09 12:00 Thu 10 12:00 Fri 11 12:00 Sat 12 12:00 Nov 13 12:00 P(s--) 操作C V P(while循环等待) P(s--) 操作C V 进程1 进程2 整形信号量进程流程图(假设进程1先开始运行)

记录信号量

除了代表资源数目的value外,增加进程链表L,用于链接所有等待进程。

// 结构
struct semapore
{
    int	value;
    PCB	L;
}
// wait
void P(semaphore *s)
{
    s.value--;
    if(s.value < 0)
    {
        block(s.L);
    }
}
// signal
void V(semaphore *s)
{
    s.value++;
    if(s.value <= 0)
    {
        wakeup(s.L);
    }
}
semaphore s;
s.value = 1;
Process:
{
    P(&s);
    操作C;
    V(&s);
}
Mon 07 12:00 Tue 08 12:00 Wed 09 12:00 Thu 10 12:00 Fri 11 12:00 Sat 12 12:00 P(s) s=0 操作C V(s) s=0 唤醒进程2 P(s) s=-1 if s<0 阻塞 P(s) 操作C V 进程1 进程2 记录型信号量

经典的进程同步问题

生产者-消费者问题

这种问题是相互合作进程关系的一种抽象。

  1. 生产者和消费者必须互斥的使用
  2. 缓冲区缓冲区空时,消费者不能读取数据
  3. 缓冲区满时,生产者不能添加数据

利用记录型信号量解决

单缓冲区

生产者进程P和消费者进程C共用一个缓冲区,P生产产品放入缓冲区,C从缓冲区取产品来消费。

单人单缓冲
多人单缓冲
放产品
取产品
放产品
取产品
放产品
取产品
放产品
取产品
放产品
取产品
消费者
缓冲区
(一次只可放一个产品)
生产者
消费者A
缓冲区
(一次只可放一个产品)
生产者A
消费者B
生产者B
消费者C
生产者C
消费者……
生产者……

单缓冲区出现主要矛盾

  • 生产者和消费者不知道缓冲区内是不是有产品,进而一直在“摸索”,占用处理机和临界资源,造成资源利用低。
    所以需要一个或多个变量给生产者和消费者“说”是不是空的。

信号量解

  • empty信号量:代表缓冲区内空缓冲区的个数,初始状态缓冲区为空,单缓冲区里empty==1表示缓冲区空。
  • full信号量:代表缓冲区内满缓冲区的个数,初始状态缓冲区为空,即full==0。

伪代码

semaphore empty = 1, full = 0;
item buffer;
void producer()
{
    while(1)
    {
        produce an itemp;
        wait(empty);	// P(empty)
        buffer = itemp;
        signal(full);	// V(full)
    }
}

void consumer()
{
    while(1)
    {
        wait(full);		// P(full)
        itemc = buffer;
        signal(empty);	// V(empty)
        consume the itemc; 
    }
}

void main()
{
    producer();consumer();	// 同时运行
}
多缓冲区
单人多缓冲
多人多缓冲
放产品
取产品
放产品
取产品
放产品
取产品
放产品
取产品
放产品
取产品
消费者
缓冲区buffer[0,n-1]
(一次只可放一个产品)
生产者
消费者A
缓冲区buffer[0,n-1]
(一次只可放一个产品)
生产者A
消费者B
生产者B
消费者C
生产者C
消费者……
生产者……

多缓冲区的主要矛盾

  • 生产者和消费者不知道应该放和拿缓冲区的哪个位置,如果是多个生产者和消费者,很有可能造成位置冲突,即一个位置“存放多个数据”。

伪代码

int in = 0, out = 0;
// 缓冲区
item buffer[n];
// 信号量
semaphore mutex = 1, empty = n, full = 0;

// 生产者
void producer()
{
	while(1)	// 始终为TRUE
    {
        produce an item nextp;
        wait(empty);	// P(empty)
        wait(mutex);	// P(mutex)
        buffer[in] = nextp;
        in = (in + 1) % n;
        signal(mutex);	// V(mutex)
        signal(full);	// V(full)
    }
}

// 消费者
void consumer()
{
    while(1)
    {
        wait(full);		// P(full)
        wait(mutex);	// P(mutex)
        nextc = buffer[out];
        out = (out + 1) % n;
        signal(mutex);	// V(mutex)
        signal(empty);	// V(empty)
        consume the item in nextc;
    }
}

void main()
{
    producer();consumer();	// 同时运行
}

无论是生产者进程还是消费者进程,signal/V操作的次序无关紧要,但**wait/P操作的次序不能随意交换,否则可能造成死锁**。

***e.g.***假如交换P操作

// 生产者
void producer()
{
	while(1)
    {
        produce an item nextp;
        wait(mutex);
        wait(empty);
        buffer[in] = nextp;
        in = (in + 1) % n;
        signal(mutex);
        signal(full);
    }
}

// 消费者
void consumer()
{
    while(1)
    {
        wait(mutex);
        wait(full);
        nextc = buffer[out];
        out = (out + 1) % n;
        signal(mutex);
        signal(empty);
        consume the item in nextc;
    }
}

按照以下顺序执行:

  1. 生产者生产产品
  2. 消费者wait(mutex)
    此时mutex=0
  3. 消费者wait(empty)
    此时full=-1,消费者被阻塞
  4. 生产者wait(mutex)
    此时mutex=-1,生产者被阻塞
    此时谁运行都将被阻塞,造成系统死锁!

为何引用mutex?empty和full不够使用?

在多缓冲区示例中,empty和mutex都可以是>1的值,所以会导致多个进程进入临界区,这样违背了进程的互斥访问。

所以仅使用empty和full,并不能实现进程的互斥访问,所以需要添加一个mutex(初始为1,即可以最多允许一个进程访问临界区),进而实现互斥访问临界区(即mutex作为进程进入临界区的“门票”)。

循环队列

在这里插入图片描述

  1. 队空判断:front==rear
  2. front(生产者-消费者问题为out)指向最先进入的满缓冲区。
  3. rear(生产者-消费者问题为in)永远指向下一个空缓冲区。

读者-写者问题

利用记录型信号量解决

类似文件读写的逻辑。

文件
允许多个读、一个写
writer
reader进程1
reader进程2
reader进程3
reder

信号量设置

w_mutex
互斥量。写者与其他读者/写者互斥地访问。
初值为1。

mutex
互斥量。互斥访问临界资源read_count。
初值为1。

变量

reader_count
读者计数,初值为0。

File file;
int read_count = 0;
semaphore w_mutex = 1, mutex = 1;

// 读者
void reader()
{
    while(1)
    {
        wait(mutex);		// 占用读者计数变量使用权
        read_count++;		// 读者计数+1
        if(read_count == 1)	// 如果+1后计数为1,则需要抢占写使用权
        	wait(w_mutex);
        signal(mutex);		// 释放读者计数变量使用权
        read(file);			// 读文件
        wait(mutex);
        read_count--;		
        if(read_count == 0)	// 当前进程-1后如为0,释放写使用权
            signal(w_mutex);
        signal(mutex);		// 释放释放读者计数变量使用权
    }
}

// 写者
void writer()
{
    while(1)
    {
        wait(w_mutex);
        write(file);
        signal(w_mutex);
    }
}

读写公平的的读者写者问题

读者优先:只要有一个读者存在,写者无法写,读不断进入,长时间无法写。

读写公平:阻止新读者继续进入

File file;
int read_count = 0;
semaphore w_mutex = 1, mutex = 1, w = 1;

// 读者
void reader()
{
    while(1)
    {
        wait(w);			// 如被抢占,则不允许读者继续进入
        wait(mutex);		// 占用读者计数变量使用权
        if(read_count == 0)	// 如果是第一个来的写者,则需要抢占修改权
        	wait(w_mutex);
        read_count++;		// 读者计数+1
        signal(mutex);		// 释放读者计数变量使用权
        signal(w);
        read(file);			// 读文件
        wait(mutex);
        read_count--;		
        if(read_count == 0)	// 如是最后写者,释放文件修改权
            signal(w_mutex);
        signal(mutex);		// 释放释放读者计数变量使用权
    }
}

// 写者
void writer()
{
    while(1)
    {
        wait(w);			
        wait(w_mutex);
        write(file);
        signal(w_mutex);
        signal(w);
    }
}

写者优先的读者-写者问题

一个写者打算写,直到最后一个读者写完后,读者才能拿到使用权。

第一个写者要拿读者的控制权。

File file;
int read_count = 0, write_count = 0;		// 读者、写者计数
semaphore 
    read_in = 1,				// 控制新读者进入的信号量
	read_count_mutex = 1, 		// 读者计数器控制权
	write_count_mutex = 1,		// 写者计数器控制权
	file = 1;					// 文件控制权

// 读者
void reader()
{
    while(1)
    {
        wait(read_in);				// 如被抢占,则不允许读者继续进入
        wait(read_count_mutex);		// 占用读者计数变量使用权
        if(read_count == 0)			// 如果是第一个来的写者,则需要抢占修改权
        	wait(file);
        read_count++;				// 读者计数+1
        signal(read_count_mutex);	// 释放读者计数变量使用权
        signal(read_in);
        read(file);					// 读文件
        wait(read_count_mutex);
        read_count--;		
        if(read_count == 0)			// 如是最后写者,释放文件修改权
            signal(file);
        signal(read_count_mutex);	// 释放释放读者计数变量使用权
    }
}

// 写者
void writer()
{
    while(1)
    {
        wait(write_count_mutex);
        if(write_count == 0)
            wait(read_in);
        write_count++;
        signal(write_count_mutex);
        wait(file);
        write(file);
        signal(file);
        wait(write_count_mutex);
        write_count--;
        if(write_count == 0)
            signal(read_in);
        signal(write_count_mutex);
    }
}

哲学家进餐问题

问题描述

有5个哲学家共用一张圆桌,分别坐在周围的5张椅子上。

在圆桌上有5个碗和**5支(不是5双)**筷子,他们的生活方式是交替地进行思考和进餐。

平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。

进餐完毕后,放下筷子继续思考。

在这里插入图片描述

利用记录型信号量解决

因为5支筷子是临界资源,每一支筷子在一段时间内只允许一位哲学家使用。

可以用一个信号量表示一只筷子。

semaphore chopstick[5] = {1, 1, 1, 1, 1};

哲学家活动的描述

semaphore chopstick[5] = {1, 1, 1, 1, 1};

void philosopher(int i)
{
    while(1)
    {
        wait(chopstick[i]);				// 拿左筷子
        wait(chopstick[(i + 1) % 5]);	// 拿右筷子
        eat;							// 进食
        signal(chopstick[i]);			// 释放左筷子
        signal(chopstick[(i+ 1) % 5]);	// 释放右筷子
        think;							// 思考
    }
}

会出现死锁的情况

假如五位哲学家同时饥饿而各自拿起右边的筷子,5个信号量chopstick均为0

当他们再试图拿左边的筷子时,无限期等待。

不是因为对筷子的抢占。

因此,解决哲学家进餐死锁,需要对筷子进行抢占


上述死锁可采取的解决方案

  1. 最多允许4个哲学家拿左边的筷子,最终保证最少有一位哲学家可以进餐,用完后释放,从而使更多的哲学家可以用餐。

  2. 规定奇数号哲学家先拿左边筷子,然后拿右边的筷子,偶数则相反。

    例如:

    1、2号哲学家竞争1号筷子;

    3、4号哲学家竞争3号筷子(竞争奇数号),获得后再去竞争偶数号筷子,

    至少能有一位哲学家获得两只筷子进餐。

  3. 仅当哲学家左右两只筷子都没被使用,才允许哲学家拿起筷子进餐。

  4. 对筷子进行编号,规定哲学家先取编号小的筷子。

  5. 如果拿左筷子后发现右筷子拿不到,则放下左筷子,隔一段时间后申请左筷子。

  6. 对哲学家分为三种状态:思考、饥饿、进食,并且一次拿到两只筷子,否则不拿。

方案1:限制并发数

semaphore chopstick[5] = {1, 1, 1, 1, 1},
	count = 4;	// 控制并发数目4

void philosopher()
{
    while(1)
    {
        wait(count);
        wait(chopstick[i]);
        wait(chopsitck[(i + 1) % 5]);
        eat;
        signal(chopsitck[(i + 1) % 5]);
        signal(chopstick[i]);
        signal(count);
        think;
    }
}

一个人吃到饭,其他人均可


方案2:奇偶

semaphore chopstick[5] = {1, 1, 1, 1, 1};

void philosopher(int i)
{
    while(1)
    {
        if(i % 2 == 1)	// 奇数
        {
             wait(chopstick[i]);				// 拿左筷子
      		 wait(chopstick[(i + 1) % 5]);		// 拿右筷子
        }
        else			// 
        {
             wait(chopstick[(i + 1) % 5]);		// 拿右筷子
             wait(chopstick[i]);				// 拿左筷子
        }
        eat;									// 进餐
        signal(chopstick[i]);					// 释放左筷子
        signal(chopstick[(i+ 1) % 5]);			// 释放右筷子
        think;									// 思考
    }
}

在这里插入图片描述

多次测试:5号哲学家最容易抢到资源,从而最先吃完释放筷子,解决了死锁。

吸烟者问题

放入
纸、草、火中的两种
取草、火
取纸、火
取纸、草
供应商
缓冲区
桌子
吸烟者1
(只有纸)
吸烟者2
(只有草)
吸烟者3
(只有火)
semaphore offer1 = 0,	// 吸烟者1需要的物品是否存在
	offer2 = 0,			// 吸烟者2需要的物品是否存在
	offer3 = 0,			// 吸烟者3需要的物品是否存在
	finish = 0;			// 物品是否被消耗掉

// 供应者
void provider()
{
    while(1)
    {
        int i = radom() % 3;	// 取随机数
        if(i == 0)
        {
            将草、火放入缓冲区;
            signal(offer1);
        }
        else if(i == 2)
        {
            将纸、火放入缓冲区;
            signal(offer2);
        }
        else
        {
            将纸、草放入缓冲区;
            signal(offer3);
        }
        wait(finish);
    }
}

// 第一种吸烟者
void smoker1()
{
    while(1)
    {
		wait(offer1);
        从缓冲区拿走物品;
        signal(finish);
        抽烟;
    }
}
          
// 第二种吸烟者
void smoker2()
{
    while(1)
    {
		wait(offer2);
        从缓冲区拿走物品;
        signal(finish);
        抽烟;
    }
}
          
// 第二种吸烟者
void smoker3()
{
    while(1)
    {
		wait(offer3);
        从缓冲区拿走物品;
        signal(finish);
        抽烟;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极客BOY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值