• 生产者与消费者模型:餐厅吃饭问题


    14天阅读挑战赛


    目录

    一、算法简介

    二、所需接口

    1.头文件

    2.接口简介

    2.1线程控制

    2.2互斥锁

    2.3条件变量

    三、实现流程

    1.顾客流程

    2.厨师流程

    3.注意事项★

    四、代码实现


    一、算法简介

            利用多线程实现生产者消费者模型,模拟实现餐厅吃饭问题。

            顾客作为消费者,负责吃饭;厨师作为生产者,负责做饭。

    二、所需接口

    1.头文件

    #include

    2.接口简介

    2.1线程控制

    (1)线程创建:

    int pthread_create(pthread_t *tid, pthread_arr_t *arr, void* (*thread_routine)(void *), void *arg);

            pthread_t *tid:传入pthread_t类型变量的地址,获取线程id;

            pthread_arr_t *arr:线程属性,通常置为NULL;

            void* (*thread_routine)(void *):函数地址,线程的入口函数

            void *arg:向线程入口函数传递的参数。

            返回值:成功,返回0;失败,返回非0。

    tid相关知识:

            每个线程都会有自己相对独立的一块空间作为自己的局部存储。

            创建线程时,返回的tid的值就是这块空间的首地址,所以通过tid就能找到这块空间,进而访问其中的数据实现对线程的控制操作。

    (2)线程终止:

    线程终止:退出线程

    1)在线程入口函数中return

            线程的入口函数运行完毕,对应线程就会退出。

    2)在任意位置调用pthread_exit函数

    void pthread_exit(void *retval);

            retval:线程的退出返回值

    3)在任意位置调用pthread_cancel函数

    int pthread_cancel(pthread_t tid);

            tid:想要退出的线程的tid

    返回值:

            如果一个线程是被取消的,则这个线程的返回值是:PTHREAD_CANCELED

    功能:取消指定的线程

    (3)线程等待:

    int pthread_join(pthread_t tid, void **retval);

            tid:要等待的线程tid;

            void **retval:用于获取线程退出返回值

    返回值:

            成功,返回0;失败,返回错误编号(非0)

    2.2互斥锁

    定义互斥锁:

            pthread_mutex_t;

    通过接口初始化:

            pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *);

                    属性通常置为NULL;

    阻塞加锁:

            int pthread_mutex_lock(pthread_mutex_t *mutex);

    解锁:

            int pthread_mutex_unlock(pthread_mutex_t *mutex);

    释放锁资源:

            int pthread_mutex_destroy(pthread_mutex_t *mutex);

    2.3条件变量

    定义条件变量:

            pthread_cond_t;

    初始化条件变量:

            int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);

    阻塞接口:

            int pthread_cond_wait( int pthread_cond_t *cond, pthread_mutex_t *mutex);

            此接口集合了三个操作:解锁、休眠,被唤醒后加锁

    唤醒接口:

            int pthread_cond_signal(pthread_cond_t cond);

            唤醒至少一个被阻塞的线程。

            int pthread_cond_broadcast(pthread_cond_t *cond);

            唤醒所有被阻塞的线程。

    释放条件变量资源:

            int pthread_cond_destroy(pthread_cond_t *cond);

    三、实现流程

    1.顾客流程

            1)加锁;

            2)判断当前是否有饭可吃

                    有,则吃饭;没有,则等待。等待前需要先解锁;被唤醒后,加锁。

            3)吃饭;

            4)解锁;

            5)唤醒厨师。

    2.厨师流程

            1)加锁;

            2)判断当前是否需要做饭

                    需要,则做饭;不需要,则等待。等待前需要先解锁;被唤醒后,加锁。

            3)做饭;

            4)解锁;

            5)唤醒顾客。

    3.注意事项★

    在多对多的情况下:

            1)因为一个厨师可能一次唤醒了多个顾客,造成多个顾客抢锁,但是只有一个顾客会抢锁成功,其他顾客则会阻塞在锁上;抢锁成功的顾客在吃完饭后解锁,时间片轮转,下一次抢到锁的则可能不是厨师,而是刚刚阻塞在锁上的顾客,则会出现“假”吃饭现象,同理也可能出现“假”做饭现象。

            解决方案:任意角色在每次加锁后,都应该重新循环上去,再次对资源进行判断是否满足获取条件。满足,则获取;不满足,则重新陷入休眠。

            2)顾客和厨师在不满足条件时,都会阻塞,加入阻塞队列等待被唤醒。但是存在一种可能:一个顾客吃完饭后,唤醒的不是厨师,而是顾客,这时两个顾客就会因为没有饭而重新陷入阻塞,从而导致程序卡死。

            解决方案:多种角色线程,则使用多个条件变量,创建多个pcb等待队列。分开等待、分开唤醒,防止错误的角色唤醒。

    四、代码实现

    1. #include<stdio.h>
    2. #include<pthread.h>
    3. int dish = 0;
    4. pthread_mutex_t mutex;
    5. pthread_cond_t cond_cus;
    6. pthread_cond_t cond_chef;
    7. void *customer(void *arg) {//顾客执行流
    8. while (1) {
    9. //1.加锁
    10. pthread_mutex_lock(&mutex);
    11. while (dish == 0) {//没有饭菜,则阻塞等待
    12. pthread_cond_wait(&cond_cus, &mutex);
    13. }
    14. //2.吃饭菜,然后解锁、唤醒厨师
    15. dish = 0;
    16. printf("吃到青椒肉丝盖饭!没吃饱,再来一份!\n");
    17. pthread_mutex_unlock(&mutex);
    18. pthread_cond_signal(&cond_chef);//唤醒厨师
    19. }
    20. }
    21. void *chef(void *arg) {
    22. while (1) {
    23. //1.加锁
    24. pthread_mutex_lock(&mutex);
    25. while (dish == 1) {//多对多时,这里必须用while判断,不能用if
    26. pthread_cond_wait(&cond_chef, &mutex);
    27. }
    28. //2.做饭菜,然后解锁、唤醒顾客
    29. dish = 1;
    30. printf("青椒肉丝盖饭好了!\n");
    31. pthread_mutex_unlock(&mutex);
    32. pthread_cond_signal(&cond_cus);//唤醒顾客
    33. }
    34. }
    35. int main() {
    36. int ret;
    37. pthread_t customer_tid[4], chef_tid[4];//顾客线程&厨师线程
    38. //初始化互斥锁和条件变量
    39. pthread_mutex_init(&mutex, NULL);
    40. pthread_cond_init(&cond_cus, NULL);
    41. pthread_cond_init(&cond_chef, NULL);
    42. //创建顾客线程&厨师线程
    43. for (int i = 0; i < 4; ++i) {
    44. ret = pthread_create(&customer_tid[i], NULL, customer, NULL);
    45. if (ret != 0) {
    46. printf("Create customer thread error!\n");
    47. return -1;
    48. }
    49. }
    50. for (int i = 0; i < 4; ++i) {
    51. ret = pthread_create(&chef_tid[i], NULL, chef, NULL);
    52. if (ret != 0) {
    53. printf("Create chef thread error!\n");
    54. return -1;
    55. }
    56. }
    57. //线程等待
    58. for (int i = 0; i < 4; ++i) {
    59. pthread_join(customer_tid[i], NULL);
    60. pthread_join(chef_tid[i], NULL);
    61. }
    62. //销毁资源
    63. pthread_mutex_destroy(&mutex);
    64. pthread_cond_destroy(&cond_cus);
    65. pthread_cond_destroy(&cond_chef);
    66. return 0;
    67. }

  • 相关阅读:
    DC/DC开关电源学习笔记(十二)Boost升压电路仿真及工程应用案例
    实时数据的处理一致性如何保证?
    MFC Windows 程序设计[233]之CPP十六进制编辑器(附源码)
    小程序源码:云之道知识付费独立线传版V2-2.4.9
    在面试中,如何回复擅长 Vue 还是 React
    面试经典-Spring篇
    MySQL最新版8.1.0安装配置教程
    深入探讨栈数据结构:定义、特性和应用
    点云 3D 数据 读取、保存 - pypcd 包
    目标检测YOLO实战应用案例100讲-基于YOLOv5的航拍图像旋转目标检测(下)
  • 原文地址:https://blog.csdn.net/m0_63020222/article/details/127435426