经典同步互斥问题—生产者与消费者(单对单)
一、问题描述
系统中有一组生产者进程和消费者进程,生产者每次生产一个产品到缓存区,消费者每次从缓存区中取出一个产品并消费。生产者与消费者公享一个缓存区(初始为空,大小为n),只有当缓存区不满时生产者才生产产品,只有当缓存区不空时消费者才消费产品。缓存区是一个临界资源,各进程必须互斥访问。
二、问题分析
- 生产者与消费者有协作关系,只有生产者生产了产品,消费者才能消费。
- 生产和消费操作要互斥访问临界区。
三、解决问题
这里的PV操作就是指信号量机制,wait() 和 signal() 访问,这里为了代码看起来简单一点就简写了。
P操作(wait访问):申请资源——若没有当前需要的资源就 block() 阻塞进程。
V操作(signal访问):释放资源——若当前资源被需要就 wakeup() 唤醒进程。
// 定义信号量
semaphore mutex = 1; // 互斥信号量,用于实现对缓冲区的互斥访问,初始值为1
semaphore empty = n; // 空缓冲区数量信号量,表示当前可用的空缓冲区数量,初始值为n(缓冲区总容量)
semaphore full = 0; // 满缓冲区数量信号量,表示当前已填充产品的缓冲区数量,初始值为0
// 生产者进程
producer(){
while(1){ // 生产者无限循环生产产品
makeProduct(); // 生产一个产品(临界区外的准备工作)
P(empty); // 申请一个空缓冲区,若没有空缓冲区则阻塞等待
P(mutex); // 申请互斥锁,确保对缓冲区的独占访问
putProduct(); // 将生产好的产品放入缓冲区
V(mutex); // 释放互斥锁,允许其他进程访问缓冲区
V(full); // 增加一个满缓冲区计数,唤醒可能等待的消费者
}
}
// 消费者进程
consumer(){
while(1){ // 消费者无限循环消费产品
P(full); // 申请一个满缓冲区,若没有产品则阻塞等待
P(mutex); // 申请互斥锁,确保对缓冲区的独占访问
getProduct(); // 从缓冲区取出一个产品
V(mutex); // 释放互斥锁,允许其他进程访问缓冲区
V(empty); // 增加一个空缓冲区计数,唤醒可能等待的生产者
useProduct(); // 使用取出的产品(临界区外的操作)
}
}
经典同步互斥问题—生产者与消费者(多对多)
一、问题描述
有一家四口人,父亲(dad),母亲(mom),儿子(son),女儿(daughter),父亲负责削苹果(放到盘子里),母亲负责剥桔子(放到盘子里),女儿爱吃橘子(只吃橘子,从盘子里拿),儿子爱吃苹果(只吃苹果,从盘子里拿)。只有父母看到盘子空时才进行自己的操作;仅当自己喜欢的水果在盘子里时,女儿和儿子才动手拿;盘子只允许放一个水果。
二、问题分析
- 由于只有一个盘子(缓存区)且只允许放一个水果,那么父母进程是互斥的。
- 父亲给儿子削苹果——父亲和儿子是同步关系,母亲给女儿剥桔子——母亲和女儿是同步关系。
三、解决问题
思考为什么这里不需要互斥锁?
如果盘子大小不为1需不需要互斥锁?
注意:进程对临界区的访问必须互斥,不然可能会发生数据覆盖!
// 信号量定义
// semaphore mutex = 1; // 此处注释掉了互斥信号量,因为盘子容量为1时无需额外互斥
semaphore apple = 0; // 苹果信号量,记录盘子里苹果的数量,初始为0
semaphore orange = 0; // 橙子信号量,记录盘子里橙子的数量,初始为0
semaphore plate = 1; // 盘子信号量,表示盘子是否为空,初始为1(盘子为空可用)
// 父亲进程(生产者:生产苹果)
dad()
{
while (1) // 无限循环,持续生产苹果
{
peelApple(); // 准备工作:削苹果(临界区外操作)
P(plate); // 申请使用盘子,若盘子被占用则等待(盘子为空时才能放苹果)
putApple(); // 将苹果放到盘子里
V(apple); // 增加苹果信号量,通知儿子盘子里有苹果
}
}
// 母亲进程(生产者:生产橙子)
mom()
{
while (1) // 无限循环,持续生产橙子
{
peelOrange(); // 准备工作:削橙子(临界区外操作)
P(plate); // 申请使用盘子,若盘子被占用则等待(盘子为空时才能放橙子)
putOrange(); // 将橙子放到盘子里
V(orange); // 增加橙子信号量,通知女儿盘子里有橙子
}
}
// 儿子进程(消费者:消费苹果)
son()
{
while (1) // 无限循环,持续消费苹果
{
P(apple); // 等待苹果信号量,若盘子里没有苹果则阻塞
getApple(); // 从盘子里拿走苹果
V(plate); // 释放盘子信号量,通知父母盘子已空
eatApple(); // 吃苹果(临界区外操作)
}
}
// 女儿进程(消费者:消费橙子)
daughter()
{
while (1) // 无限循环,持续消费橙子
{
P(orange); // 等待橙子信号量,若盘子里没有橙子则阻塞
getOrange(); // 从盘子里拿走橙子
V(plate); // 释放盘子信号量,通知父母盘子已空
eatOrange(); // 吃橙子(临界区外操作)
}
}
经典同步互斥问题—读者与写者(读优先、写优先、读写公平)
一、问题描述
有读者与写者两个并发进程,共享一个文件,当多个读者访问共享数据时不会产生影响,当多个写者访问共享数据时会有影响。
① 允许多个读者对文件进行读操作。
② 不允许多个写者对文件进行写操作。
③ 任意一个写者进行写操作时不允许,其他写操作和读操作执行。
二、问题分析
- ② ③ => 写者与读者互斥、写者与写者互斥,① => 读者与读者不互斥。
三、解决问题
1、读优先:让第一个读者加锁,最后一个读者解锁 —— 添加一个记录读者的变量。
注意:该写法写者可能会饿死。
// 信号量定义
semaphore rw = 1; // 用于控制读写操作互斥的信号量,确保写操作与读/写操作互斥
int count = 0; // 记录当前正在读取文件的读者数量
semaphore mutex = 1; // 用于保护count变量的互斥信号量,确保对count的操作原子性
// 写者进程
writer() {
while(1) { // 写者无限循环执行写操作
P(rw); // 申请rw信号量,若有读者或其他写者则阻塞等待
// 确保此时只有当前写者能访问文件
writerFile(); // 执行写文件操作(临界区)
V(rw); // 释放rw信号量,允许其他读者或写者访问文件
}
}
// 读者进程
reader() {
while(1) { // 读者无限循环执行读操作
P(mutex); // 申请mutex信号量,保护对count变量的操作
if(count == 0) { // 如果是第一个读者
P(rw); // 申请rw信号量,阻止写者访问(此时开始有读者)
}
count++; // 读者数量加1
V(mutex); // 释放mutex信号量,允许其他读者修改count
readFile(); // 执行读文件操作(临界区,可多个读者同时进行)
P(mutex); // 申请mutex信号量,保护对count变量的操作
count--; // 读者数量减1
if(count == 0) { // 如果是最后一个读者
V(rw); // 释放rw信号量,允许写者访问(此时已无读者)
}
V(mutex); // 释放mutex信号量,允许其他读者修改count
}
}
2、写优先:让第一个写者加锁,最后一个写者解锁 —— 添加一个记录写者的变量。
注意:该写法读者可能会饿死。
3、读写公平:通过添加一个写进程的锁,实现读写访问按照执行顺序。
关于这个写法,我在网上看着有说是读写公平的,也有说是写优先的 ,希望有大佬能给出一个标准的写法。
// 信号量与共享变量定义
semaphore mutex; // 用于保护读者计数count的互斥信号量,确保对count的操作原子性
int count = 0; // 记录当前正在读取文件的读者数量
semaphore rw = 1; // 控制读写互斥的信号量,保证写操作独占访问或读者并发访问
semaphore w = 1; // 写者优先控制信号量,实现写者对后续读者的优先级
// 写者进程
writer()
{
while (1)
{ // 写者无限循环执行写操作
P(w); // 申请写者优先信号量
// 若有其他写者已获取w,当前写者会排队等待(写者间公平)
// 若获取成功,将阻止新读者进入,实现写者优先
P(rw); // 申请读写互斥信号量
// 确保此时没有读者或其他写者正在访问文件,实现独占
writerFile(); // 执行写文件操作(临界区,独占访问)
V(rw); // 释放读写互斥信号量,允许其他进程竞争访问权
V(w); // 释放写者优先信号量,允许后续写者或读者进入
}
}
// 读者进程
reader()
{
while (1)
{ // 读者无限循环执行读操作
P(w); // 尝试获取写者优先信号量
// 若有写者正在操作或等待,读者会阻塞(体现写者优先)
// 只有当没有写者竞争时,读者才能继续
P(mutex); // 申请互斥锁,保护对count变量的修改
if (count == 0)
{ // 当第一个读者进入时
P(rw); // 获取读写互斥信号量,阻止写者访问
}
count++; // 读者计数加1,记录当前活跃读者数量
V(mutex); // 释放互斥锁,允许其他读者修改count
V(w); // 释放写者优先信号量,允许其他进程(写者或读者)竞争
readFile(); // 执行读文件操作(可多个读者同时进行)
P(mutex); // 申请互斥锁,准备修改读者计数
count--; // 读者离开,计数减1
if (count == 0)
{ // 当最后一个读者离开时
V(rw); // 释放读写互斥信号量,允许写者访问
}
V(mutex); // 释放互斥锁,完成读者计数修改
}
}
经典同步互斥问题—哲学家就餐问题
一、问题描述
一张圆桌边坐着5名哲学家,每两名哲学家之间摆着1根筷子,两根筷子之间是一碗米饭。哲学家们在思考时不影响其他人,只有当哲学家饿了时会试图拿起左右两根筷子进餐。若筷子在他人手上,则需要等待。哲学家只有拿起两根筷子才能进餐,进餐完毕进入思考。
二、问题分析
- 一共有5名哲学家,5根筷子,哲学家对左右两个哲学家的筷子访问是互斥的。
- 如果每个哲学家都拿起一根筷子会发生什么?死锁。
- 那怎么解决死锁的问题呢?我们让n - 1个哲学家拿起筷子,那么肯定至少会有一个哲学家进餐,只需要等待这个哲学家吃完,就可以将进程持续下去了。
三、解决问题
// 定义5个信号量表示5根筷子,初始值为1(表示可用)
semaphore chopstick[5] = {1, 1, 1, 1, 1};
// 定义互斥信号量,用于保证取筷子操作的原子性
semaphore mutex = 1;
// 哲学家i的进程函数
Pi() {
while(1) { // 哲学家的行为是一个无限循环
P(mutex); // 进入临界区前获取互斥锁,确保取筷子过程的原子性
// 拿起左边的筷子(第i根筷子)
// P操作:如果筷子可用则获取,否则阻塞等待
P(chopstick[i]);
// 拿起右边的筷子(第(i+1)%5根筷子,处理最后一个哲学家的边界情况)
P(chopstick[(i + 1) % 5]);
V(mutex); // 退出临界区,释放互斥锁
吃饭; // 哲学家进行吃饭操作
// 放下左边的筷子,V操作表示资源可用
V(chopstick[i]);
// 放下右边的筷子
V(chopstick[(i + 1) % 5]);
思考; // 哲学家进行思考操作
}
}
1709

被折叠的 条评论
为什么被折叠?



