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

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

一、算法简介


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


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


二、所需接口


1.头文件

#include<pthread.h>


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等待队列。分开等待、分开唤醒,防止错误的角色唤醒。


四、代码实现


#include<stdio.h>        
#include<pthread.h>
int dish = 0;
pthread_mutex_t mutex;
pthread_cond_t cond_cus;
pthread_cond_t cond_chef;
void *customer(void *arg) {//顾客执行流
  while (1) {
    //1.加锁
    pthread_mutex_lock(&mutex);
    while (dish == 0) {//没有饭菜,则阻塞等待
      pthread_cond_wait(&cond_cus, &mutex);
    }
    //2.吃饭菜,然后解锁、唤醒厨师
    dish = 0;
    printf("吃到青椒肉丝盖饭!没吃饱,再来一份!\n");
    pthread_mutex_unlock(&mutex);
    pthread_cond_signal(&cond_chef);//唤醒厨师
  }
}
void *chef(void *arg) {
  while (1) {
    //1.加锁
    pthread_mutex_lock(&mutex);
    while (dish == 1) {//多对多时,这里必须用while判断,不能用if
      pthread_cond_wait(&cond_chef, &mutex);
    }
    //2.做饭菜,然后解锁、唤醒顾客
    dish = 1;
    printf("青椒肉丝盖饭好了!\n");
    pthread_mutex_unlock(&mutex);
    pthread_cond_signal(&cond_cus);//唤醒顾客
  }
}
int main() {
  int ret;
  pthread_t customer_tid[4], chef_tid[4];//顾客线程&厨师线程
  //初始化互斥锁和条件变量
  pthread_mutex_init(&mutex, NULL);
  pthread_cond_init(&cond_cus, NULL);
  pthread_cond_init(&cond_chef, NULL);
  //创建顾客线程&厨师线程
  for (int i = 0; i < 4; ++i) {
    ret = pthread_create(&customer_tid[i], NULL, customer, NULL);
    if (ret != 0) {
      printf("Create customer thread error!\n");
      return -1;
    }
  }
  for (int i = 0; i < 4; ++i) {
    ret = pthread_create(&chef_tid[i], NULL, chef, NULL);
    if (ret != 0) {
      printf("Create chef thread error!\n");
      return -1;
    }
  }
  //线程等待
  for (int i = 0; i < 4; ++i) {
    pthread_join(customer_tid[i], NULL);
    pthread_join(chef_tid[i], NULL);
  }
  //销毁资源
  pthread_mutex_destroy(&mutex);
  pthread_cond_destroy(&cond_cus);
  pthread_cond_destroy(&cond_chef);
  return 0;
}
相关文章
|
3月前
|
消息中间件 负载均衡 Kafka
【Kafka消费秘籍】深入了解消费者组与独立模式,掌握消息消费的两种超能力!
【8月更文挑战第24天】Apache Kafka是一款高性能的分布式消息系统,支持灵活多样的消费模型以适应不同的应用场景。消息按主题组织,每个主题可划分为多个分区,确保消息顺序性。本文深入探讨了Kafka中的两大核心消费模式:消费者组(Consumer Group)和独立消费者(Standalone Consumer)。消费者组允许多个消费者协同工作,实现负载均衡及故障恢复,是最常用的消费模式。独立消费者模式则适用于需要高度定制化处理逻辑的场景,如消息重放等。通过对比这两种模式的特点和提供的示例代码,开发者可以根据具体需求选择最合适的消费策略,从而更好地利用Kafka构建高效的数据流应用程序。
75 3
|
3月前
|
设计模式 安全 Python
生产者与消费者模式
生产者与消费者模式
|
5月前
|
消息中间件 网络协议 物联网
消息队列 MQ产品使用合集之如何让消费者不从最开始进行消费,而是从最后一条消息开始消费
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
|
设计模式 安全
生产者与消费者模型
生产者与消费者模型
99 0
生产者与消费者模型
|
Java Nacos 网络架构
服务消费者 | 学习笔记
快速学习服务消费者。
服务消费者 | 学习笔记
|
消息中间件 存储 调度
RabbitMQ学习笔记 02、生产者与消费者、多消费者平均压力
RabbitMQ学习笔记 02、生产者与消费者、多消费者平均压力
RabbitMQ学习笔记 02、生产者与消费者、多消费者平均压力
笔试题:请写出一个消费者生产者模型
“请写出一个生产者消费者模型。一个生产者,一个消费者,生产者生产一个,消费者消费一个”。
一个简单的生产者和消费者客服实现
一个简单的生产者和消费者客服实现
140 0