实验五 进程同步问题实现

一、实验目的

利用实验四提供的方法和例子,解决进程同步相关问题,例如:生产者消费者问题,哲学家进餐等问题。

二、实验环境

硬件环境:计算机一台,局域网环境;

软件环境:Linux Ubuntu操作系统,gcc编译器

三、实验内容

运用实验四中提供的进程同步方法实现如下问题:

  1. 生产者消费者问题
  • 问题描述:一组生产者进程向一组消费者进程提供产品,两类进程共享一个由n个缓冲区组成的有界缓冲池,生产者进程向空缓冲池中投放产品,消费者进程从放有数据的缓冲池中取得产品并消费掉。

  • 只要缓冲池未满,生产者进程就可以把产品送入缓冲池;只要缓冲池未空,消费者进程便可以从缓冲池中取走产品。

  • 但禁止生产者进程向满的缓冲池再输送产品,也禁止消费者进程从空的缓冲池中提取产品。

  • 为了防止对缓冲池重复操作,故规定在任何时候,只有一个主体可以访问缓冲池。

    /*
     * @Description: 多个生产者消费者问题
     * @version: 
     * @Author: 
     * @Date: 2021-04-30 13:53:49
     * @LastEditors: Please set LastEditors
     * @LastEditTime: 2021-04-30 15:59:14
     */
    
    // NOTE:
    // *sem_init()初始化无名信号量,sem_open()是初始化有名信号量
    // *第一个参数是指向信号量的指针
    // *第二个参数为0,表示信号量只在这一进程的所有线程之间共享,否则,是进程间共享
    // *第三个参数是初始值
    
    // *线程互斥锁的创建,上锁与解锁
    // * int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)
    // * 第二个参数是锁的属性
    // *int pthread_mutex_lock(pthread_mutex_t *mutex)
    // *int pthread_mutex_unlock(pthread_mutex_t *mutex)
    
    // *int pthread_create((pthread_t *thread,  pthread_attr_t *attr,  void *(*start_routine)(void  *), void *arg)
    // * 线程标识符
    // * 线程属性
    // * 线程函数地址
    // * 传给函数的参数
    
    // ? 代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <semaphore.h>
    
    #define PRODUCER_NUM 5 //生产者数目
    #define CONSUMER_NUM 5 //消费者数目
    #define POOL_SIZE 11   //缓冲池大小
    
    int pool[POOL_SIZE];   //缓冲区
    int head = 0;          //缓冲池读取指针
    int rear = 0;          //缓冲池写入指针
    sem_t empty_sem;       //同步信号信号量,表示缓冲区有可用空间
    sem_t full_sem;        //同步信号量,表示缓冲区有可用产品
    pthread_mutex_t mutex; // 缓冲区互斥访问,线程互斥锁
    // 生产者
    void *producer_fun(void *arg)
    {
        while (1)
        {
            sleep(1);
            /* 空间-- */
            sem_wait(&empty_sem);
            // 关闭互斥信号量
            pthread_mutex_lock(&mutex);
            //生产者往缓冲池中写入数据
            pool[rear] = 1;
            rear = (rear + 1) % POOL_SIZE;
            printf("producer %d write to pool\n", (int)arg);
            printf("pool size is %d\n", (rear - head + POOL_SIZE) % POOL_SIZE);
            // 打开互斥信号量
            pthread_mutex_unlock(&mutex);
            // 产品++
            sem_post(&full_sem);
        }
    }
    // 消费者
    void *consumer_fun(void *arg)
    {
        while (1)
        {
            int data;
            sleep(10);
            // 检查是否是产品
            sem_wait(&full_sem);
            // 关闭互斥信号量
            pthread_mutex_lock(&mutex);
            //消费者从缓冲池读取数据
            data = pool[head];
            head = (head + 1) % POOL_SIZE;
            printf("consumer %d read from pool\n", (int)arg);
            printf("pool size is %d\n", (rear - head + POOL_SIZE) % POOL_SIZE);
            // 打开互斥信号量
            pthread_mutex_unlock(&mutex);
            // 空闲位置++
            sem_post(&empty_sem);
        }
    }
    
    int main()
    {
        int i;
        pthread_t producer_id[PRODUCER_NUM];    // 生产者线程数组
        pthread_t consumer_id[CONSUMER_NUM];    // 消费者线程数组
        pthread_mutex_init(&mutex, NULL);       //初始化互斥量
        sem_init(&empty_sem, 0, POOL_SIZE - 1); //初始化信号量empty_sem为缓冲池大小
        sem_init(&full_sem, 0, 0);              //初始化信号量full_sem为0,开始时缓冲池中没有数据
        for (i = 0; i < PRODUCER_NUM; i++)
        {
            //创建生产者线程
            pthread_create(&producer_id[i], NULL, producer_fun, (void *)i);
            //创建消费者线程
            pthread_create(&consumer_id[i], NULL, consumer_fun, (void *)i);
        }
        for (i = 0; i < PRODUCER_NUM; i++)
        {
            pthread_join(producer_id[i], NULL);
            pthread_join(consumer_id[i], NULL);
        }
        exit(0);
    }
    

    在这里插入图片描述

  1. 哲学家进餐问题
  • 有5位哲学家倾注毕生精力用于思考和吃饭,他们围坐在一张圆桌旁,在圆桌上有5个碗和5支筷子。每位哲学家的行为通常是思考,当其感到饥饿时,便试图取其左右最靠近他的筷子进餐。只有他拿到两支筷子后才能进餐,进餐完毕后,释放两支筷子并继续思考。

  • 要求:采取合适的方法,防止出现死锁的问题

    /*
     * @Description: 哲学家进餐问题
     * @version: 
     * @Author: 
     * @Date: 2021-04-30 14:51:12
     * @LastEditors: Please set LastEditors
     * @LastEditTime: 2021-05-14 14:13:54
     */
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <semaphore.h>
    #include <unistd.h>
    // 至多只允许4位哲学家竞争筷子,这样就一定有一个哲学家可以拿到两支筷子
    #define N 5                               // 五个哲学家
    #define TIME_EAT 5                        // 吃饭的时间
    #define TIME_THINK 5                      // 思考的时间
    #define LIMIT 4                           // 同一时间只允许4人用餐
    #define left(phi_id) (phi_id + N - 1) % N // 宏函数,计算哲学家左手边的筷子,注意id可能为0,所以要+N
    #define right(phi_id) (phi_id + 1) % N    // 宏函数,计算哲学家右手边的筷子
    sem_t chopstick[N]; // 筷子互斥信号量数组
    sem_t limit;        // 竞争人数
    
    void thinking(int id)
    {
        sleep(TIME_THINK);
        printf("philosopher[%d] is thinking...\n", id);
    }
    
    void eating(int id)
    {
        sleep(TIME_EAT);
        printf("philosopher[%d] is eating...\n", id);
    }
    
    void take_forks(int id)
    {
        //获取左右两边的筷子
        sem_wait(&chopstick[left(id)]);
        sem_wait(&chopstick[right(id)]);
        printf("philosopher[%d]  take_forks...\n", id);
    }
    
    void put_down_forks(int id)
    {
        printf("philosopher[%d] is put_down_forks...\n", id);
        sem_post(&chopstick[left(id)]);
        sem_post(&chopstick[right(id)]);
    }
    
    void *philosopher_work(void *arg)
    {
        int id = *(int *)arg;
        printf("philosopher init [%d] \n", id);
        while (1)
        {
            thinking(id);
            // 检查同时竞争的哲学家是否达到限制,是则阻塞等待,否则进入竞争
            sem_wait(&limit);
            take_forks(id);
            // 已经拿到了筷子,退出竞争
            sem_post(&limit);
            eating(id);
            // 进食完毕,释放筷子
            put_down_forks(id);
        }
    }
    
    int main()
    {
        // 线程数组,相当于N个哲学家
        pthread_t phiTid[N];
        int i;
        int *id = (int *)malloc(sizeof(int) * N);
    
        // 初始化筷子信号量,初始情况是,所有筷子都空闲
        for (i = 0; i < N; i++)
            sem_init(&chopstick[i], NULL, 1);
        // 初始化信号量,初始值是LIMIT,无人参与竞争
        sem_init(&limit, NULL, LIMIT);
    
        for (i = 0; i < N; ++i)
        {
            id[i] = i;
            // 生成的thread id是0,1,2,3,4,(iny*)->(void*)
            pthread_create(&phiTid[i], NULL, philosopher_work, (void *)(&id[i]));
        }
    
        while (1)
            ;
        exit(0);
    }
    

    在这里插入图片描述

  1. 和尚打水问题
  • 某寺庙,有小和尚,老和尚若干。有一水缸,由小和尚提水入缸供老和尚饮用。水缸可容10桶水,水取自同一井中。水井径窄,每次中能容下一个桶取水。水桶总数为3个。每人一次取缸水仅为1桶,且不可同时进行。

    /*
     * @Description: 和尚打水问题
     * @version: 
     * @Author: 
     * @Date: 2021-04-30 15:27:58
     * @LastEditors: Please set LastEditors
     * @LastEditTime: 2021-04-30 16:46:49
     */
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <semaphore.h>
    #include <stdbool.h>
    // mutex1是井的互斥信号量
    // mutex2是缸的互斥信号量
    // amount是桶的互斥信号量
    // empty和full是缸的同步信号量
    sem_t mutex1, mutex2, amount, empty, full;
    int fullcount = 0;
    void *LittleMonk(void *p)
    {
        while (true)
        {
            // 检查是否空,若空则打水,否则阻塞等待
            sem_wait(&empty);
            // 检查是否有桶可用,有则去访问井,否则阻塞等待
            sem_wait(&amount);
            // 检查有没有其他小和尚正在访问井
            sem_wait(&mutex1);
            // 井边提水
            printf("第%d个小和尚在水井提水\n", *(int *)p);
            // 打完水后释放井的信号量,离开井
            sem_post(&mutex1);
            // 检查是否有其他和尚正在访问缸,若没有则倒水,否则阻塞等待
            sem_wait(&mutex2);
            // 小和尚在水缸旁倒水
            printf("水缸已有%d桶水,第%i个小和尚在水缸旁倒水\n", fullcount, *(int *)p);
            // 缸内水的数量++
            fullcount++;
            // 倒水的小和尚离开缸,释放资源
            sem_post(&mutex2);
            // 放下水桶,释放桶资源
            sem_post(&amount);
            // 水缸里水的桶数加1
            sem_post(&full);
        }
    }
    void *BigMonk(void *p)
    {
        while (true)
        {
            // 检查缸内是否有水,有则打水,否则阻塞等待
            sem_wait(&full);
            // 老和尚检查是否有水桶可用,没有水桶则等待
            sem_wait(&amount);
            // 水缸旁有人则等待,没有则开始打水
            sem_wait(&mutex2);
            printf("\t水缸已有%d桶水,第%d个大和尚在水缸旁提水\n", fullcount, *(int *)p);
            // 打完水,缸内水的数量--
            fullcount--;
            // 老和尚离开缸,释放缸互斥信号量
            sem_post(&mutex2);
            // 老和尚放下水桶,是否同互斥信号量
            sem_post(&amount);
            // 空闲位置++
            sem_post(&empty);
        }
    }
    
    int main()
    {
        int i, j;
        // 保存进程的ID
        pthread_t little[20], big[20];
        //初始化信号量
        // 井互斥锁默认为1
        sem_init(&mutex1, 0, 1);
        // 缸互斥锁默认为1
        sem_init(&mutex2, 0, 1);
        // 开始时有3个水桶供使用
        sem_init(&amount, 0, 3);
        // 开始时水缸里没有水,设置为0
        sem_init(&full, 0, 0);
        // 开始时水缸里最多能装10桶水,设置为10
        sem_init(&empty, 0, 10);
        //创建多个老和尚进程
        for (i = 1; i <= 4; ++i)
        {
            int *id = &i;
            pthread_create(&big[i], NULL, BigMonk, (void *)id);
        }
        //创建多个小和尚进程
        for (j = 1; j <= 10; ++j)
        {
            int *id = &j;
            pthread_create(&little[j], NULL, LittleMonk, (void *)id);
        }
        while (true)
            ;
        exit(0);
    }
    

    在这里插入图片描述

四、实验总结

结合实验,谈谈你对进程同步的理解和体会。

  • 进程同步是指并发执行的进程因直接制约关系而需相互等待,相互合作,以实现各进程按相互协调的速度向前推进的过程。同步过程中进程与进程之间有序合作,相互清楚对方的存在及其作用,进程之间直接合作完成任务。
Logo

更多推荐