一、算法简介
利用多线程实现生产者消费者模型,模拟实现餐厅吃饭问题。
顾客作为消费者,负责吃饭;厨师作为生产者,负责做饭。
二、所需接口
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; }