经典同步互斥问题解析

经典同步互斥问题—生产者与消费者(单对单)

一、问题描述

        系统中有一组生产者进程和消费者进程,生产者每次生产一个产品到缓存区,消费者每次从缓存区中取出一个产品并消费。生产者与消费者公享一个缓存区(初始为空,大小为n),只有当缓存区不满时生产者才生产产品,只有当缓存区不空时消费者才消费产品。缓存区是一个临界资源,各进程必须互斥访问。

二、问题分析

  1. 生产者与消费者有协作关系,只有生产者生产了产品,消费者才能消费。
  2. 生产和消费操作要互斥访问临界区。

三、解决问题

这里的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. 由于只有一个盘子(缓存区)且只允许放一个水果,那么父母进程是互斥的。
  2. 父亲给儿子削苹果——父亲和儿子是同步关系,母亲给女儿剥桔子——母亲和女儿是同步关系。

三、解决问题

思考为什么这里不需要互斥锁? 

如果盘子大小不为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. ② ③ => 写者与读者互斥、写者与写者互斥,① => 读者与读者不互斥。

三、解决问题

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根筷子,两根筷子之间是一碗米饭。哲学家们在思考时不影响其他人,只有当哲学家饿了时会试图拿起左右两根筷子进餐。若筷子在他人手上,则需要等待。哲学家只有拿起两根筷子才能进餐,进餐完毕进入思考。

二、问题分析

  1. 一共有5名哲学家,5根筷子,哲学家对左右两个哲学家的筷子访问是互斥的。
  2. 如果每个哲学家都拿起一根筷子会发生什么?死锁。
  3. 那怎么解决死锁的问题呢?我们让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]);
        
        思考;  // 哲学家进行思考操作
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值