410操作系统学习笔记——进程与线程、处理机调度、同步与互斥(PV操作)、死锁(三)

简介: 410操作系统学习笔记——进程与线程、处理机调度、同步与互斥(PV操作)、死锁

3.同步与互斥

3.1.进程同步、进程互斥的概念

1.同步(直接制约):两个或者多个进程需要按照某种顺序执行

2.互斥(间接制约):A进程访问某种临界资源时,若进程B也想访问该临界资源,进程B则必须等待进程A访问完成后才能访问该临界资源(一段时间内只能有一个进程访问

①进入区:检查是否可以进入临界区,若能进入临界区,则设置正在访问临界资源的标志(即上锁),防止其他进程进入该临界区

②临界区:访问临界资源的代码(例如需要打印机执行打印操作,则对打印机进行写输出的代码,就在临界区中)

③退出区:解除正在访问临界资源的标志(即解锁)

④剩余区:做其他处理

进入区和退出区是实现互斥的代码段;临界区是访问临界资源的代码段

do {
    entry section;    //进入区
    critical section;    //临界区
    exit section;    //退出区
    remainder section;    //剩余区
} while(true)

3.互斥应该遵循两个原则:

空闲让进:当临界区空闲时,有进程请求进入则立即允许该进程访问

忙则等待:当临界区已有进程正在访问时,其余想要访问该临界区的进程则必须等待

有限等待:等待访问临界区的进程必须在有限时间内访问该临界区(防止饥饿)

让权等待:当进程不能访问临界区时,必须让出处理机37c53507092e4287821f536759c4f85b.png

3.2.进程互斥的软件实现方法

3.2.1.单标志法

1.算法思想:两个进程在访问完临界区后会把该临界区的使用权转交给另一个进程临界区的使用权只能由另一个进程赋予

int turn = 0;    //turn = 0表示允许P0进程进入;turn = 1表示允许P1进程进入
//P0进程
while (turn != 0) ;    //进入区,判断是否当前允许turn是否为0
critical section;    //临界区,访问临界资源
turn = 1;    //退出区,将临界区的访问权让给P1
remainder section;    //剩余区
//P1进程
while (turn != 1) ;    //进入区,判断是否当前允许turn是否为1
critical section;    //临界区
turn = 1;    //退出区
remainder section;    //剩余区

2.执行过程:设初始状态下turn = 0,即刚开始允许访问临界区的是P0进程

①若首先让P1上处理机运行,则P1进程的代码将会卡在while循环处turn != 1为真,死循环)直到P1的时间片用完

②切换至P0后,P0进程的while循环不满足条件(turn != 0为假),执行临界区代码,开始访问临界资源;若直到P0的时间片用完,P0还未退出临界区,切换回P1进程,P1依然会卡在while循环处直到P1时间片用完(因为turn仍为0,即临界区的访问权还是P0的)

③当P0访问完临界区后,在退出区的代码中将会修改turn = 1(即将临界区的访问权让给P1)

3.缺点:违反空闲让进原则临界区的使用权只能由另一个进程赋予,即P0若想访问临界区,必须在P1访问完临界区,将turn改为0

3.2.2.双标志先检查法(先检查后上锁)

1.算法思想:设置一个bool数组,用于标记各个进程是否想要访问临界区;在各个进程访问临界区前,依次检查该bool数组,看是否有其他进程想要访问临界区,若没有,则将自己的标志改为true,并开始访问临界区

bool flag[2];    //标记各个进程是否想要访问临界区
flag[0] = false;
flag[1] = false;    //初始化P0和P1为不想要访问临界区
//P0进程:
while(flag[1]);    //进入区,判断P1是否想要进入临界区
flag[0] = true;    //将自身flag改为true,表示自己想要进入临界区
critical section;    //临界区
flag[0] = false;    //退出区,将自身flag改为false
remainder section;
//P1进程:
while(flag[0]);    //进入区,判断P0是否想要进入临界区
flag[1] = true;    //将自身flag改为true,表示自己想要进入临界区
critical section;    //临界区
flag[1] = false;    //退出区,将自身flag改为false
remainder section;

进入区做了两件事:①需要检查对方是否想要进入临界区 ②如果对方并不想进入临界区,则表达自己想要进入临界区的意愿(即上锁,并在在退出临界区后解锁)

2.缺点:违反忙则等待的原则(原因是检查和上锁是分成两步做的,即不是一气呵成的,而进程是并发执行的,可能发生进程的切换

设初始状态下两个flag都为false,并且从P0进程开始运行:

①P0的while不满足条件(flag[1] = false),准备执行flag[0] = true

②由于进程是并发执行的,若此时切换到P1程序,P1的while循环也不满足条件(flag[0] = false,P0并未来得及修改自身的标志位)

③切换回P0进程执行flag[0] = true,并且进入临界区

④切换回P1进程执行flag[1] = true,并且进入临界区

此时P0和P1都已进入临界区,即违反忙则等待原则

3.2.3.双标志后检查法(先上锁后检查)

1.算法思想:与双标志先检查法相比,相同点:进入区仍是分两步,即上锁和检查;不同点:双标志先检查法是①检查 ②上锁,而双标志后检查法是①上锁 ②检查

bool flag[2];    //标记各个进程是否想要访问临界区
flag[0] = false;
flag[1] = false;    //初始化P0和P1为不想要访问临界区
//P0进程:
flag[0] = true;    //将自身flag改为true,表示自己想要进入临界区
while(flag[1]);    //进入区,判断P1是否想要进入临界区
critical section;    //临界区
flag[0] = false;    //退出区,将自身flag改为false
remainder section;
//P1进程:
flag[1] = true;    //将自身flag改为true,表示自己想要进入临界区
while(flag[0]);    //进入区,判断P0是否想要进入临界区
critical section;    //临界区
flag[1] = false;    //退出区,将自身flag改为false
remainder section;

2.缺点:违背空闲让进和有限等待(将会导致饥饿)原则

设初始状态下两个flag都为false,并且从P0进程开始运行:

①P0将自己的flag改为true,表示自己想要访问临界区,此时发生进程切换,开始执行P1进程

②P1将自己的flag改为true,表示自己想要访问临界区

③此时P0和P1的flag都为true,即P0和P1都想要访问临界区,而现实情况是两个进程都还没能访问临界区(也意味着无法将自己的flag改为false)

④P0和P1继续执行就会分别卡在自己的循环语句中(对方的flag无法更改,死循环)进而导致饥饿

3.2.4.Peterson算法

1.算法思想:如果双方都想访问临界区,则让对方访问临界区

bool flag[2];    //标记进程是否想要访问临界区,初始值为false
int turn = 0;    //turn表示优先让哪个进程进入临界区
//P0进程:
flag[0] = true;    //表示自己想要进入临界区
turn = 1;    //表示优先让对方进入临界区
while (flag[1] && turn == 1);    //如果对象想要进入,并且是自己让对方进入的,则等待
critical section;
flag[0] = false;    //退出临界区后,表示自己不想进入临界区
remainder section;
//P1进程:
flag[1] = true;
turn = 0;
while (flag[0] && turn == 0);
critical section;
flag[1] = false;
remainder section;

2.缺点:并未遵循让权等待原则(若想要进入临界区的进程无法进入临界区,则会卡在while循环,即不断地查询是否能进入临界区)4b1eb40230824d8a81c440f92a2209bd.png

3.3.进程互斥的硬件实现方法

3.3.1.中断屏蔽方法

1.实现方法:在访问临界区前关中断,在访问临界区后开中断(不允许中断等于不发生进程切换)

2.缺点:

①不适用于多处理机:关中断指令对一个处理机有效,即若A处理机执行关中断指令使进程a访问临界区C后,B处理机上的进程b此时仍可以访问该临界区C

②只适用于操作系统内核进程,不适用于用户进程:开/关中断指令属于特权指令,需要在内核态下才能运行

3.3.2.TestAndSet指令(TS,也称TSL,即TestAndSetLock)

1.实现方法:①通过硬件实现  ②将上锁和检查“一气呵成的执行”(双标志先后检查的升级版)

//bool类型的共享变量lock表示当前临界区是否被加锁
//true表示已被加锁,false表示未被加锁
bool TestAndSet (bool *lock) {
    bool old;
    old = *lock;    //用old存放原来lock的值(上锁还是未上锁)
    *lock = true;    //无论之前上锁与否,都将上锁
    return old;    //返回原来lock的值
}
while (TestAndSet (&lock));
critical section;    //临界区
lock = false;    //退出区,解锁
remainder section;    //剩余区

通过old变量记录之前是否上锁,无论上锁与否都将进行上锁操作:若之前上锁,TestAndSet的上锁操作不影响临界区上锁的状态;若之前未上锁,TestAndSet的上锁操作表示自己将要访问临界区

(1)若刚开始lock的值为false(即未上锁):

①TestAndSet函数的返回值为false,即while语句的条件为假,跳出循环进入临界区

②在①执行的过程中,即在TestAndSet函数中通过*lock = true(即上锁)

③退出临界区将lock重新改为false(即解锁)

(2)若刚开始lock的值为true(即上锁):

TestAndSet函数的返回值为True,即while语句的条件为真,进程将会卡在while循环,直到正在访问临界区的进程退出临界区(即等待正在访问临界区的进程执行lock = false)

2.优点:适用多处理机环境

缺点:违反让权等待的原则(若有进程正在访问临界区,则想要访问该临界区的其他进程将会卡在while循环语句一直等待其退出,因为while此时是死循环)

3.3.3.swap指令(也称exchange指令或XCHG指令)

1.实现方法:①通过硬件实现 ②“一气呵成”的交换两个变量的值

//Swap指令的作用是交换两个变量的值
Swap (bool *a, bool *b) {
    bool temp;
    temp = *a;
    *a = *b;
    *b = temp;
}
//Swap指令的实现互斥访问的示例
//lock表示当前临界区是否上锁
bool old = true;
while (old == true) Swap (&lock, &old);
critical section;    //临界区代码段
lock = false;    //退出区,将lock改为false
remainder section;    //剩余区代码段

通过old变量记录当前是否上锁(即当前是否由于别的进程访问临界区),并通过lock变量上锁

2.swap指令和TestAndSet指令逻辑上是等价的,因此,它们的优缺点相同

3.4.互斥锁

1.一个进程在进入临界区时通过acquire()获得锁,在退出临界区时通过release()释放锁

2.通过bool类型的available表示当前是否可以获得锁:available = true时,则可通过acquire()获得锁,并且available改为false;available = false时,则acquire()会被阻塞,直到被改为true

acquire() {    //获得锁
    while (!available);    //判断当前锁是否可用
    available = false;    //将锁的状态修改为不可用
}
release() {    //释放锁
    available = true;    //将锁的状态修改为可用
}

3.互斥锁的缺点为忙等(即违反让权等待原则):若已有进程正在访问临界区,则其他想要访问该临界区的进程将会一直卡在while循环中,等待其退出临界区,直到时间片用完前不会让出处理机

4.自旋锁:需要连续忙等的互斥锁(TSL指令、SWAP指令和单标志法)

5.特点:

违反让权等待原则(有限等待,在该进程时间片用完时,仍会被剥夺处理机的使用权,切换至另一个进程)

②适用多处理机系统:例如进程P1正在访问临界区A,进程P2等待访问临界区A(自旋等待)

P2自旋等待即可以在P1退出临界区A时直接进入该临界区A,而不用切换环境(切换环境需要大量的系统开销

③不适用单处理机系统:单处理机系统只能有一个进程占用处理机,即P1只可能在自己的时间片完成对临界区的访问,也就是说P2不可能在自己时间片等到P1访问完临界区

3.5.信号量机制

1.信号量:一种变量,用来表示系统中某种资源的数量

2.原语:一气呵成的执行,不可中断(通过关/开中断指令实现)

3.wait(S)和signal(S)原语:对信号量进行操作

①wait和signal分别为函数名,信号量S为传入的参数

②wait和signal原语简称为P、V操作,可以分别简写为P(S)和V(S)

3.5.1.整型信号量

1.对信号量的操作只能有三种:①初始化 ②P操作 ③V操作

2.wait原语的作用是:

循环检查当前资源是否够用(即满足访问需求):若够用,则跳出循环;若不够用,则将会不断检查(卡在循环)直到够用

②将该资源分配给当前进程(执行②时必然该资源够用),即系统资源数 - 1

3.signal原语的作用是将进程占用的系统资源归还,即系统资源数 + 1

4.在P0进程执行wait原语后(即开始使用打印机),若切换到其他进程,其他进程仍然会执行wait原语,但会卡在wait原语中的while循环语句(因为当前S的值为0),即循环等待P0使用完打印机

5.信号量本质上是双标志先检查法,只是执行上能够一气呵成:

①while(S <= 0) 代表检查

②S = S - 1 代表上锁

6.违背让权等待原则(与记录型信号量主要的区别):若有进程正在访问临界区,其它想要该临界区的进程访问将会卡在循环fcfdd6b013a34da18f076d870d1b78cb.png

3.5.2.记录型信号量

1.记录型信号量解决整型信号量中存在忙等现象

①在执行wait原语时,若剩余资源不够分配,将会把该进程从运行态变为阻塞态,并且把该进程挂到信号量S对应的等待队列

②执行signal原语时,多了一个当前剩余资源的判断(在归还系统资源后),若当前剩余资源仍为<= 0,说明还有其他进程在等待使用该系统资源,则从信号量S对应的等待队列中唤醒一个进程,并将该进程从阻塞态变为运行态(阻塞态→就绪态→运行态)a5abb03aa7da4e20976a2aa1462207af.png

2.wait(S)和signal(S)对应P(S)和V(S)

3.S.value的初值表示某种系统资源的数量

4.对信号量S的一次P操作表示有一个进程请求该系统资源

P操作要执行S.value--(表示该系统资源的数量 - 1)

②当S.value < 0时,表示该系统资源分配完毕

③当S.value < 0时,需要执行block(阻塞原语,并非每次P操作都要执行主动放弃处理机的使用权(运行态→阻塞态

(因此,遵循让权等待原则)

5.对信号量S的一次V操作表示有一个进程释放该系统资源

V操作要执行S.value++(表示该系统资源的数量 + 1)

②当S.value <= 0时(在执行S.value--后),说明还有其他进程在等待使用该系统资源,需要执行weak(唤醒原语,并非每次V操作都要执行)将等待队列的第一个进程唤醒,并将其上处理机运行(阻塞态 → 就绪态 → 运行态442d3b13df7e4aed88fff769a68615a5.png

3.6.用信号量实现进程互斥、同步和前驱关系

3.6.1.用信号量实现进程互斥

1.用单独的一个变量mutex实现互斥访问:mutex = 1表示该资源当前没有进程访问;mutex <= 0表示该资源有进程正在访问

2.semaphore代表的是记录型信号量,即带有排队和阻塞功能,不会忙等

3.不同的临界资源需要设置不同的互斥信号量:例如打印机(mutex1)和摄像头(mutex2)

4.PV操作必须成对出现:缺P无法互斥;缺V将会无法唤醒

c71f9caa400544bb846cce6c20bddff0.png

3.6.2.用信号量实现进程同步fc641c171a744089808e0710bf96e460.png

3.6.3.用信号量实现前驱关系 cb49fc7af54c42389f0780dd43943b69.pngf4d8ee01a5a840b5a551d52c54d15de6.png

3.7.生产者-消费者42955d54494e49baa167292377ba8fc3.png

1.缓冲区满:生产者不能生产(阻塞,同步关系),消费者可以消费

2.缓冲区不满(非空):生产者可以生产,消费者可以消费

3.缓冲区空:生产者可以生产,消费者不能消费(阻塞,同步关系)

4.各进程对缓冲区的访问是互斥的:防止对缓冲区的某个相同区域进行操作(缓冲区是临界资源

5.互斥关系在同一个进程内进行(即PV操作在同一个进程内);同步关系在不同的进程间运行(即一个进程P,另一个进程V)

//互斥信号量,用于消费者和生产者互斥访问缓冲区
semaphore mutex = 1;
//同步信号量,表示缓冲区的空闲数量,初始缓冲区为空,即empty = n
//生产者每生产一个就-1,即P(empty)
//消费者每消费一个就+1,即V(empty)
semaphore empty = n;
//同步信号量,表示缓冲区的当前数量,初始缓冲区为空,即full = 0
//生产者每生产一个就+1,即V(full)
//消费者每消费一个就-1,即P(full)
semaphore full = 0;
producer() {
    while(1) {
        生产一个产品;
        P(empty);    //检查缓冲区的空闲数量,有空闲则 - 1;没有则阻塞
        P(mutex);    //开始访问缓冲区(互斥)
        把产品放入缓冲区;
        V(mutex);    //结束访问缓冲区
        V(full);    //缓冲区当前数量 + 1
    }
}
consumer() {
    while(1) {
        P(full);    //检查缓冲区的当前数量,有产品则 - 1;没有则阻塞
        P(mutex);    //开始访问缓冲区(互斥)
        将产品从缓冲区中取出;
        V(mutex);    //结束访问缓冲区
        V(empty);    //缓冲区空闲数量 + 1
    }
}

6.实现互斥的P操作一定要在实现同步的P操作之后:防止死锁

7.生产/使用产品最好在PV操作之前或之后,这样就可以减少临界区的代码数量,降低访问临界区的时间,从而提高整体的实行速度

8.V操作并不会导致进程阻塞aa7b1a9e319945a4891a277c114b5b65.png

3.8.多生产者-多消费者96a623b716384226bc1e438e72601918.png

1.互斥关系:对盘子(缓冲区)的访问是互斥的

2.同步关系:

①父亲先放苹果,女儿后取苹果

②母亲先放橘子,儿子后取橘子

③女儿/儿子取出水果后(缓冲区we),父亲/母亲才能放水果

semaphore orange = 0;    //同步关系,盘子中橘子的数量
semaphore apple = 0;    //同步关系,盘子中苹果的数量
semaphore plate = 1;    //同步关系,盘子中水果的数量
semaphore mutex = 1;    //互斥关系,实现对盘子(缓冲区)的互斥访问
dad() {
    while(1) {
        准备一个苹果;
        P(plate);    //检查盘子是否还能放水果,能则 - 1;不能则阻塞
        P(mutex);    //开始使用盘子(互斥访问临界资源)
        将苹果放入盘子;
        V(mutex);    //结束使用盘子
        V(apple);    //苹果数量 + 1
    }
}
mom() {
    while(1) {
        准备一个橘子;    
        P(plate);    //检查盘子是否还能放水果,能则 - 1;不能则阻塞
        P(mutex);    //开始使用盘子(互斥访问临界资源)
        将橘子放入盘子;
        V(mutex);    //结束使用盘子
        V(orange);    //橘子数量 + 1
    }
}
son() {
    while(1) {
        P(apple);    //先检查苹果数量是否够,够则 - 1;不够则阻塞
        P(mutex);    //开始使用盘子(互斥访问临界资源)
        将苹果取出盘子;
        V(mutex);    //结束使用盘子
        V(plate);    //盘子中水果数量 - 1
        吃掉苹果;
    }
}
daughter() {
    while(1) {
        P(orange);    //先检查橘子数量是否够,够则 - 1;不够则阻塞
        P(mutex);    //开始使用盘子(互斥访问临界资源)
        将橘子取出盘子;
        V(mutex);    //结束使用盘子
        V(plate);    //盘子中水果数量 - 1
        吃掉橘子;
}

3.当缓冲区(此题为盘子)数量 = 1时,有时可以不需要用mutex实现互斥访问(此题就行);而当缓冲区数量 > 1时,则必须要用mutex实现互斥访问(最好都使用mutex)

4.要用事件角度思考同步关系 dee7ef6b936146c6aeddf2af6465369b.png

3.9.吸烟者问题f3230b5fb75242368213430e00e52083.png

1.互斥关系:供应者和吸烟者需要互斥的访问桌子(此题中缓冲区大小为1,因此不需要单独设置mutex实现互斥访问)

2.同步关系:

(1)吸烟者①②③都需要在供应者分别在桌上提供相应的组合才能取走东西(吸烟者①对应offer1;吸烟者②对应offer2;吸烟者③对应offer3。即每个吸烟者对应的offer信号不同

(2)每个吸烟者完成后需要给一个完成信号告诉供应者可以开始下次供应(即每个吸烟者的完成信号相同

3.通过对i信号变量的自增和取余操作实现吸烟者①②③的轮流吸烟操作

semaphore offer1 = 0;    //同步信号,桌上组合一的数量
semaphore offer2 = 0;    //同步信号,桌上组合二的数量
semaphore offer3 = 0;    //同步信号,桌上组合三的数量
semaphore finish = 0;    //同步信号,当前抽烟者是否完成抽烟
int i = 0;    //用于实现吸烟者轮流抽烟
provider() {
    while(1) {
        if (i == 0) {    //当i = 0时
            将组合一放到桌上;
            V(offer1);    //同步信号,提供组合一,即组合一数量 + 1
        }
        else if (i == 1) {//当i = 1时
            将组合二放到桌上;    
            V(offer2);    //同步信号,提供组合二,即组合二数量 + 1
        }
        else {//当i = 2时
            将组合三放到桌上;
            V(offer3);    //同步信号,提供组合三,即组合三数量 + 1
        }
        P(finish);    //同步信号,等待某个吸烟者完成吸烟
        i = (i + 1) % 3;    //每次循环先执行i = i + 1,然后i对3取余
    }
}
smoker1() {
    while(1) {
        P(offer1);    //同步信号,检查桌上的组合是不是组合一,是,则拿走;不是,则阻塞
        拿走桌上的组合一;
        卷烟;
        抽烟;
        V(finish);    //完成吸烟,给供应者发出完成信号
    }
}
smoker2() {
    while(1) {
        P(offer2);    //检查桌上的组合是不是组合二,是,则拿走;不是,则阻塞
        拿走桌上的组合二;
        卷烟;
        抽烟;
        V(finish);    //同步信号,完成吸烟,给供应者发出完成信号
    }
}
smoker3() {
    while(1) {
        P(offer3);    //同步信号,检查桌上的组合是不是组合三,是,则拿走;不是,则阻塞
        拿走桌上的组合三;
        卷烟;
        抽烟;
        V(finish);    //同步信号,完成吸烟,给供应者发出完成信号
    }
}

3.10.读者写者问题

1.允许多个读者可以同时对文件进行读操作:读操作不会改变数据

2.同一时间只允许写者对文件进行写操作:同时写,发生覆盖

3.任一写者在完成写操作前不允许其他写者或者读者工作:同时写,发生覆盖;同时读写,导致读出的数据不一致

4.写者进行写操作前,已有的写者和读者必须停止工作:同时写,发生覆盖;同时读写,导致读出的数据不一致

5.互斥关系:写-读、写-写读-读可以同步进行

6.申明一个count变量用于记录当前有几个读者正在进行读操作:由第一个将要进行读操作的读者负责读/写互斥访问操作,即上锁,并且每个将要进行读操作的读者都需要判断自己是不是第一个将要进行读操作的读者,即判断count是否为0

7.对count变量的判断和加锁需要一气呵成,即需要用mutex实现对count访问的互斥操作:如果不能一气呵成进行,则可能发生当count = 0时,在读者A对count进行判断后,执行上锁操作前,切换成读者B进程,读者B也对count进行判断,然后进行上锁操作,时就将导致读者A阻塞(因为读者B进行P(rw)操作后,rw = 0,切换回读者A进程后,读者A也要进行P(rw),而此时rw = 0,阻塞,需要等待读者B及后续可能存在的其他读者进程完成读操作才能进行读者A的读操作)

8.读写应该公平,防止写进程饿死:增加一个同步信号w,w初始值为1;其作用就是当有写者被阻塞时,该写者之后出现的读者进程将被阻塞,等待①之前的读者全部读完 ②读者开始写并写完 ,这些才能被唤醒,即执行读操作(即实现按顺序进行读/写操作)

①读者A→读者B:读者B在读者A执行V(W)前会被阻塞在P(W),读者A执行V(W)后读者B被唤醒

②写者A→写者B:写者B在写者A执行V(W)前会被阻塞在P(W),写者A执行V(W)后写者B被唤醒

③写者A→读者B:读者B在写者A执行V(W)前会被阻塞在P(W),写者A执行V(W)后读者B被唤醒

④读者A→写者B→读者C:读者A开始读文件时,也就意味着按顺序执行了P(W)、P(RW)和V(W);写者B可以正常执行P(W),但会被阻塞在P(RW)读者C将会被阻塞在P(W)

读者A在读完文件后(即执行V(RW)后),写者B将会被唤醒;在读者B写完文件后(即执行V(W)后),读者C将会被唤醒

如果没有添加同步信号w,将会写者B将会在读者C后执行

⑤写者A→读者B→写者C:写者A开始写文件时,也就意味着按顺序执行了P(W)和P(RW);读者B将会被阻塞在P(W)写者C也将会阻塞在P(W)

由于信号量具有排队功能,读者B执行P(W)后W = -1,写者C执行P(W)后W = -2(即读者B在写者C之前执行P(W)),写者A在写完文件后(即执行V(W)后),先唤醒读者B,读者B在读完文件后(即执行V(W)后),再唤醒写者C

semaphore w = 1;    //互斥信号,实现读/写公平
semaphore rw = 1;    //互斥信号,实现读/写不同时进行
semaphore mutex = 1;    //互斥信号,实现读者对count变量的互斥访问
int count = 0;    //标记当前有几个读者正在进行读操作
writer() {
    while(1) {
        P(w);    //检查是否有其他读/写者,有则排队
        P(rw);    //检查是否有读者正在读
        写操作;
        V(rw);    
        V(w);
    }
}
reader() {
    while(1) {
        P(w);    //检查是否有其他读/写者,有则排队
        P(mutex);    //互斥访问count变量
        if (!count) P(rw);    //若是第一个读者,则上锁
        count++;    //读者数量 + 1
        V(mutex);
        V(w);
        读操作;
        P(mutex);    //互斥访问count变量
        count--;    //读者数量 - 1
        if (!count) V(rw);    //若是最后一个读者,则解锁
        V(mutex);
    }
}


相关文章
|
1天前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
9 2
|
1天前
|
消息中间件 算法 Linux
深入理解操作系统之进程管理
【10月更文挑战第30天】在数字时代的浪潮中,操作系统作为计算机系统的核心,扮演着至关重要的角色。本文将深入浅出地探讨操作系统中的进程管理机制,从进程的概念入手,逐步解析进程的创建、调度、同步与通信等关键过程,并通过实际代码示例,揭示这些理论在Linux系统中的应用。文章旨在为读者提供一扇窥探操作系统深层工作机制的窗口,同时激发对计算科学深层次理解的兴趣和思考。
|
2天前
|
消息中间件 算法 调度
深入理解操作系统:进程管理与调度策略
【10月更文挑战第29天】本文将带领读者深入探讨操作系统中的核心组件之一——进程,并分析进程管理的重要性。我们将从进程的生命周期入手,逐步揭示进程状态转换、进程调度算法以及优先级调度等关键概念。通过理论讲解与代码演示相结合的方式,本文旨在为读者提供对进程调度机制的全面理解,从而帮助读者更好地掌握操作系统的精髓。
10 1
|
2天前
|
算法 调度 UED
深入理解操作系统中的进程调度
【10月更文挑战第29天】探索进程调度的奥秘,本文将带你深入了解在操作系统中如何管理和控制多个并发执行的程序。从简单的调度算法到复杂的多级反馈队列,我们将逐步揭示如何优化系统性能和提高资源利用率。准备好一起揭开进程调度的神秘面纱吧!
|
2天前
|
调度 Python
深入浅出操作系统:进程与线程的奥秘
【10月更文挑战第28天】在数字世界的幕后,操作系统悄无声息地扮演着关键角色。本文将拨开迷雾,深入探讨操作系统中的两个基本概念——进程和线程。我们将通过生动的比喻和直观的解释,揭示它们之间的差异与联系,并展示如何在实际应用中灵活运用这些知识。准备好了吗?让我们开始这段揭秘之旅!
|
6天前
|
消息中间件 算法 调度
深入理解操作系统:进程管理的艺术
【10月更文挑战第25天】在数字世界的幕后,操作系统扮演着至关重要的角色,它如同一位精心策划的指挥家,协调着硬件与软件之间的和谐交响。本文将带领读者走进操作系统的核心——进程管理,探索它是如何在幕后默默支撑起整个计算系统的运行。我们将从进程的基本概念出发,逐步深入到进程调度、同步以及死锁处理等高级话题,旨在为读者提供一次深入浅出的技术之旅。
|
6天前
|
算法 调度
探索操作系统的心脏:内核与进程管理
【10月更文挑战第25天】在数字世界的复杂迷宫中,操作系统扮演着关键角色,如同人体中的心脏,维持着整个系统的生命力。本文将深入浅出地剖析操作系统的核心组件——内核,以及它如何通过进程管理来协调资源的分配和使用。我们将从内核的概念出发,探讨它在操作系统中的地位和作用,进而深入了解进程管理的机制,包括进程调度、状态转换和同步。此外,文章还将展示一些简单的代码示例,帮助读者更好地理解这些抽象概念。让我们一起跟随这篇文章,揭开操作系统神秘的面纱,理解它如何支撑起我们日常的数字生活。
|
7天前
|
算法 大数据 Linux
深入理解操作系统之进程调度算法
【10月更文挑战第24天】本文旨在通过浅显易懂的语言,带领读者深入了解操作系统中的进程调度算法。我们将从进程的基本概念出发,逐步解析进程调度的目的、重要性以及常见的几种调度算法。文章将通过比喻和实例,使复杂的技术内容变得生动有趣,帮助读者建立对操作系统进程调度机制的清晰认识。最后,我们还将探讨这些调度算法在现代操作系统中的应用和发展趋势。
|
2天前
|
算法 调度 开发者
探索操作系统的核心:进程管理与调度
【10月更文挑战第29天】本文深入探讨了操作系统中至关重要的一环——进程管理。通过浅显易懂的语言,我们将了解到什么是进程,进程如何被创建和管理,以及操作系统如何决定哪个进程应该获得CPU时间。文章还将揭示进程调度对系统性能的影响,并分享一些优化技巧。无论你是编程新手还是有一定经验的开发者,这篇文章都将为你提供宝贵的知识。
|
7天前
|
Linux 调度
探索操作系统核心:进程与线程管理
【10月更文挑战第24天】在数字世界的心脏,操作系统扮演着至关重要的角色。它不仅是计算机硬件与软件之间的桥梁,更是管理和调度资源的大管家。本文将深入探讨操作系统的两大基石——进程与线程,揭示它们如何协同工作以确保系统运行得井井有条。通过深入浅出的解释和直观的代码示例,我们将一起解锁操作系统的管理奥秘,理解其对计算任务高效执行的影响。