Linux下线程经典问题(生产者消费者问题,哲学家问题...)
线程经典问题
本月任务
一. 解决生产者消费者问题
二. 解决哲学家吃饭问题
三. 实现进程池/线程池
在编译运行程序之后需要加上 -lpthread
生产者消费者问题
问题:生产者消费者共享缓冲区,生产者向缓冲区中放数据,消费者从缓冲取中取数据,当缓冲区中被放满时,生产者进程就必须进入挂起状态,直到消费者从缓冲中取走数据时,生产者才能继续向缓冲区中存放数据,同样当缓冲取中没有数据时,消费者进程就必须进入挂起休眠状态,直到生产者向缓冲区中放入数据时,消费者才能被唤醒继续从缓冲区中取走数据。
生产者消费者问题,也称有限缓冲问题,是一个多线程同步问题的经典案例。
解决这个问题之前,我们应该了解一下PV操作(这篇文章很好理解)
# include <stdio.h>
# include <pthread.h>
# include <unistd.h>
# include <stdlib.h>#define N 100
#define true 1
#define producerNum 10
#define consumerNum 5typedef int semaphore;
typedef int item;item buffer[N] = {0};
int in = 0;
int out = 0;
int proCount = 0;
semaphore mutex = 1, empty = N, full = 0, proCmutex = 1;void * producer(void * a)
{while(true) {while( proCmutex <= 0 );proCmutex--;proCount++;printf("生产一个产品ID%d, 缓冲区位置为%d\n",proCount,in);proCmutex++;while( empty <= 0 ) {printf("缓冲区已满!\n");}empty--;while( mutex <= 0 );mutex--;buffer[in] = proCount;in = (in + 1) % N;mutex++;full++;sleep(1);}
}void * consumer(void *b)
{while(true) {while( full <= 0 ) {printf("缓冲区为空!\n");}full--;while( mutex <= 0 );mutex--;int nextc = buffer[out];buffer[out] = 0;//消费完将缓冲区设置为0out = (out + 1) % N;mutex++;empty++;printf("\t\t\t\t消费一个产品ID%d,缓冲区位置为%d\n", nextc,out);sleep(1);}
}int main()
{pthread_t threadPool[producerNum+consumerNum];int i;for(i = 0; i < producerNum; i++) {pthread_t temp;if ( pthread_create(&temp, NULL, producer, NULL) == -1 ) {printf("ERROR, fail to create producer%d\n", i);exit(1);}threadPool[i] = temp;}//创建生产者进程放入线程池for(i = 0; i < consumerNum; i++) {pthread_t temp;if ( pthread_create(&temp, NULL, consumer, NULL) == -1 ) {printf("ERROR, fail to create consumer%d\n", i);exit(1);}threadPool[i+producerNum] = temp;}//创建消费者进程放入线程池void * result;for(i = 0; i < producerNum+consumerNum; i++) {if ( pthread_join(threadPool[i], &result) == -1 ) {printf("fail to recollect\n");exit(1);}}//运行线程池return 0;
}
哲学家吃饭问题
问题:有五个哲学家绕着圆桌坐,每个哲学家面前有一盘面,两人之间有一支筷子,这样每个哲学家左右各有一支筷子。哲学家有2个状态,思考或者拿起筷子吃饭。如果哲学家拿到一只筷子,不能吃饭,直到拿到2只才能吃饭,并且一次只能拿起身边的一支筷子。一旦拿起便不会放下筷子直到把饭吃完,此时才把这双筷子放回原处。如果,很不幸地,每个哲学家拿起他或她左边的筷子,那么就没有人可以吃到饭了。
哲学家进餐问题是一个多线程运用的经典例子,涉及到线程同步/互斥,临界区访问问题以及死锁问题。
吃饭时使用筷子:
- 拿一双筷子才能吃
- 每次只允许拿一支筷子
- 只能拿身边的筷子
- 吃完才放下筷子
5个哲学家可能每个人都拿起自己的左筷子,但是却无法拿到自己的右筷子。既无法释放自己的筷子,也等不到别人的筷子完成自己的活动,最终形成死锁。
死锁:两个或多个进程无限期地等待永远不会发生的条件的一种系统状态(结果:每个进程都永远阻塞)
在此问题中死锁为:每个哲学家都无限期的等待邻座哲学家放下筷子!
而邻座哲学家没有吃完饭前不会放下筷子!
邻座哲学家缺一支筷子永远无法吃完饭!
方法一:
每个哲学家对应一个线程,程序中定义一个互斥量,对于每个线程进行访问其他哲学家状态时用互斥量进行加锁,这样也就避免了死锁的产生,访问到该哲学家处于饥饿时,同时旁边两位科学家并未处于进餐状态时,他就拿起左右两边的叉子进行吃饭,吃饭一段时间后,就放下叉子进行思考,思考一段时间后处于饥饿状态,重新开始试图拿起叉子吃饭,代码如下
semaphore chopstick[5]={1,1,1,1,1};
semaphore room=4;
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
#include<time.h>#define N 5 //哲学家数量#define LEFT(i) (i+N-1)%N //左手边哲学家编号
#define RIGHT(i) (i+1)%N //右手边哲家编号#define HUNGRY 0 //饥饿
#define THINKING 1 //思考
#define EATING 2 //吃饭#define U_SECOND 1000000 //1秒对应的微秒数
pthread_mutex_t mutex; //互斥量int state[N]; //记录每个哲学家状态
//每个哲学家的思考时间,吃饭时间,思考开始时间,吃饭开始时间
clock_t thinking_time[N], eating_time[N], start_eating_time[N], start_thinking_time[N];
//线程函数
void *thread_function(void *arg);int main()
{pthread_mutex_init(&mutex, NULL);pthread_t a,b,c,d,e;//为每一个哲学家开启一个线程,传递哲学家编号pthread_create(&a,NULL,thread_function,"0");pthread_create(&b,NULL,thread_function,"1");pthread_create(&c,NULL,thread_function,"2");pthread_create(&d,NULL,thread_function,"3");pthread_create(&e,NULL,thread_function,"4");//初始化随机数种子srand((unsigned int)(time(NULL)));while(1){;}
}void *thread_function(void *arg)
{char *a = (char *)arg;int num = a[0] - '0'; //根据传递参数获取哲学家编号int rand_time; while(1) {//关键代码加锁pthread_mutex_lock(&mutex);//如果该哲学家处于饥饿 并且 左右两位哲学家都没有在吃饭 就拿起叉子吃饭if (state[num] == HUNGRY && state[LEFT(num)] != EATING && state[RIGHT(num)] != EATING) {state[num] = EATING;start_eating_time[num] = clock(); //记录开始吃饭时间eating_time[num] = (rand() % 5 + 5) * U_SECOND; //随机生成吃饭时间//输出状态printf("state: %d %d %d %d %d\n",state[0],state[1],state[2],state[3],state[4]);//printf("%d is eating\n",num);} else if (state[num] == EATING) {//吃饭时间已到 ,开始思考if (clock() - start_eating_time[num] >= eating_time[num]) {state[num] = THINKING;//printf("%d is thinking\n",num);printf("state: %d %d %d %d %d\n",state[0],state[1],state[2],state[3],state[4]);start_thinking_time[num] = clock(); //记录开始思考时间thinking_time[num] = (rand() % 10 + 10) * U_SECOND; //随机生成思考时间}} else if (state[num] == THINKING) {//思考一定时间后,哲学家饿了,需要吃饭if (clock() - start_thinking_time[num] >= thinking_time[num]) {state[num] = HUNGRY;printf("state: %d %d %d %d %d\n",state[0],state[1],state[2],state[3],state[4]);// printf("%d is hungry\n",num);}}pthread_mutex_unlock(&mutex); }
}
方法二:
通过互斥信号量 mutex 对哲学家进餐之前取左侧和右侧筷子的操作进行保护,可以防止死锁的出现。
# include <stdio.h>
# include <stdlib.h>
# include <malloc.h>
# include <time.h>
# include <unistd.h>
# include <pthread.h>
# include <semaphore.h># define N 5sem_t chopsticks[N]; //设置5种信号量,有5种不同类型的资源,每一种有1个,这样便于理解,因为每个哲学家需要的资源不同pthread_mutex_t mutex; //定义互斥锁int philosophers[N] = {0, 1, 2, 3, 4}; //代表5个哲学家的编号void delay (int len) {int i = rand() % len;int x;while (i > 0) {x = rand() % len;while (x > 0) {x--;}i--;}
}void *philosopher (void* arg) {int i = *(int *)arg;int left = i;//左筷子的编号和哲学家的编号相同int right = (i + 1) % N;//右筷子的编号为哲学家编号+1while (1) {printf("哲学家%d正在思考问题\n", i);delay(60000);printf("哲学家%d饿了\n", i);pthread_mutex_lock(&mutex);//加锁sem_wait(&chopsticks[left]);//此时这个哲学家左筷子的信号量-1之后>=0时,表示能继续执行。printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n", i, left);sem_wait(&chopsticks[right]);printf("哲学家%d拿起了%d号筷子\n", i, right);pthread_mutex_unlock(&mutex);//解锁printf("哲学家%d现在有两支筷子,开始进餐\n", i);delay(60000);sem_post(&chopsticks[left]);printf("哲学家%d放下了%d号筷子\n", i, left);sem_post(&chopsticks[right]);printf("哲学家%d放下了%d号筷子\n", i, right);}
}int main (int argc, char **argv) {srand(time(NULL));pthread_t philo[N];//信号量初始化for (int i=0; i<N; i++) {sem_init(&chopsticks[i], 0, 1);}pthread_mutex_init(&mutex,NULL);//初始化互斥锁//创建线程for (int i=0; i<N; i++) {pthread_create(&philo[i], NULL, philosopher, &philosophers[i]);}//挂起线程for (int i=0; i<N; i++) {pthread_join(philo[i], NULL);}//销毁信号量for (int i=0; i<N; i++) {sem_destroy(&chopsticks[i]);}pthread_mutex_destroy(&mutex);//销毁互斥锁return 0;
}# include <stdio.h>
# include <stdlib.h>
# include <malloc.h>
# include <time.h>
# include <unistd.h>
# include <pthread.h>
# include <semaphore.h># define N 5sem_t chopsticks[N]; //设置5种信号量,有5种不同类型的资源,每一种有1个,这样便于理解,因为每个哲学家需要的资源不同pthread_mutex_t mutex; //定义互斥锁int philosophers[N] = {0, 1, 2, 3, 4}; //代表5个哲学家的编号void delay (int len) {int i = rand() % len;int x;while (i > 0) {x = rand() % len;while (x > 0) {x--;}i--;}
}void *philosopher (void* arg) {int i = *(int *)arg;int left = i;//左筷子的编号和哲学家的编号相同int right = (i + 1) % N;//右筷子的编号为哲学家编号+1while (1) {printf("哲学家%d正在思考问题\n", i);delay(60000);printf("哲学家%d饿了\n", i);pthread_mutex_lock(&mutex);//加锁sem_wait(&chopsticks[left]);//此时这个哲学家左筷子的信号量-1之后>=0时,表示能继续执行。printf("哲学家%d拿起了%d号筷子,现在只有一支筷子,不能进餐\n", i, left);sem_wait(&chopsticks[right]);printf("哲学家%d拿起了%d号筷子\n", i, right);pthread_mutex_unlock(&mutex);//解锁printf("哲学家%d现在有两支筷子,开始进餐\n", i);delay(60000);sem_post(&chopsticks[left]);printf("哲学家%d放下了%d号筷子\n", i, left);sem_post(&chopsticks[right]);printf("哲学家%d放下了%d号筷子\n", i, right);}
}int main (int argc, char **argv) {srand(time(NULL));pthread_t philo[N];//信号量初始化for (int i=0; i<N; i++) {sem_init(&chopsticks[i], 0, 1);}pthread_mutex_init(&mutex,NULL);//初始化互斥锁//创建线程for (int i=0; i<N; i++) {pthread_create(&philo[i], NULL, philosopher, &philosophers[i]);}//挂起线程for (int i=0; i<N; i++) {pthread_join(philo[i], NULL);}//销毁信号量for (int i=0; i<N; i++) {sem_destroy(&chopsticks[i]);}pthread_mutex_destroy(&mutex);//销毁互斥锁return 0;
}
线程池实现
# include <stdio.h>
# include <stdlib.h>
# include <pthread.h>
# include <errno.h>
# include <sys/types.h>
# include <fcntl.h>
# include <string.h>
# include <time.h>
# include <signal.h>
# include <sys/wait.h>void *thread_routine();
void create(int num);
void add(void(*routine)(void *), void* arg);
void destroy();
void func1();typedef void Func(void*);typedef struct th_queue {void* arg;Func* routine; struct th_queue *next;
} thpool_queue;typedef struct threadpool { //线程池int flag; // 0不销毁 1销毁int num; // 线程池大小pthread_t *ID;pthread_cond_t cond; // 条件变量pthread_mutex_t mutex; // 互斥锁thpool_queue *head; // 指向队列指针
} thpool;static thpool * pool = NULL;int main(int argc,char *argv[])
{system("clear");int i;create(10); //线程池里创建10个线程for (i = 0; i < 20; i++) {add(&func1,NULL);}sleep(5);destroy();
}void *thread() { //线程函数thpool_queue *run;while (1) {pthread_mutex_lock(&pool->mutex); while((pool->head == NULL) && ( pool->flag == 0)) { //此时没有任务且不销毁线程池pthread_cond_wait(&pool->cond,&pool->mutex); //抢到锁的线程等待,其他线程在锁外边阻塞}if(pool->flag != 0) {pthread_mutex_unlock(&pool->mutex);pthread_exit(0);}run = pool->head; //不销毁,将任务添加到队列pool->head = pool->head->next; //让任务指针指向下一个pthread_mutex_unlock(&pool->mutex);run->routine(run->arg);free(run);}
}void create(int num) { //线程创建函数int i; pool = (thpool *)malloc(sizeof(thpool)); if( !pool )perror("malloc error!");pool->flag = 0;pool->num = num; pool->ID = (pthread_t*)malloc(num*sizeof(pthread_t));pool->head = NULL;pthread_mutex_init(&pool->mutex,NULL);pthread_cond_init(&pool->cond,NULL);for (i = 0;i < num;i++) {pthread_create(&pool->ID[i],NULL,thread,NULL); //创建线程}
}void add(void(*func)(void *), void* arg) {thpool_queue *run,*task;run = (thpool_queue*)malloc(sizeof(thpool_queue));run->routine = func;run->arg = arg;run->next = NULL;pthread_mutex_lock(&pool->mutex); //对队列操作保证只有一个线程task = pool->head;if( !task ) {pool->head = run; //任务是第一个任务} else {while (task->next != NULL) //不是第一个添加到最后task = task->next;task->next = run;}pthread_cond_signal(&pool->cond);pthread_mutex_unlock(&pool->mutex);
}void destroy() { //销毁线程池printf("The threadpool is being destroyed!\n");int i;thpool_queue *task; if(pool->flag != 0) //先判断是否已经销毁return;pool->flag = 1; //1,则销毁pthread_mutex_lock(&pool->mutex); pthread_cond_broadcast(&pool->cond); //唤醒全部线程pthread_mutex_unlock(&pool->mutex); for (i = 0; i < pool->num; i++) {pthread_join(pool->ID[i],NULL); //等待所有线程都结束}free(pool->ID);while (pool->head) {task = pool->head;pool->head = pool->head->next;free(task); //释放每一个任务}pthread_mutex_destroy(&pool->mutex); //销毁锁pthread_cond_destroy(&pool->cond); //销毁条件变量free(pool); sleep(5);puts("Destroyed!\n");
}void func1() {printf("thread %u is running\n",pthread_self());sleep(3);
}}
Linux下线程经典问题(生产者消费者问题,哲学家问题...)相关推荐
- Linux下实现多线程的生产者消费者问题
Linux下实现多线程的生产者消费者问题 一.原理的理解 生产者-消费者问题是一个经典的线程同步问题,该问题最早由Dijkstra提出,用以演示他提出的信号量机制.在同一个线程地址空间内执行的两个线程 ...
- 生产者消费者_【线程通信】生产者消费者模型
1生产者消费者模型介绍 生产者消费者模型,是每一个学习多线程的的人都需要知道的模型; 大致情况就是:有两个线程,一个负责生产产品,一个消费产品,两者公用同一块内存区域,也就是产品放在了同一块内存上面, ...
- linux下线程错误码表
linux下线程错误码在/usr/include/asm-generic/errno-base.h中查看
- 线程通信之生产者消费者阻塞队列版
线程通信之生产者消费者阻塞队列版 ProdConsumer_BlockQueueDemo.java import java.util.concurrent.ArrayBlockingQueue; im ...
- Java多线程之线程通信之生产者消费者阻塞队列版
Java多线程之线程通信之生产者消费者传统版和阻塞队列版 目录 线程通信之生产者消费者传统版 线程通信之生产者消费者阻塞队列版 1. 线程通信之生产者消费者传统版 题目: 一个初始值为零的变量,两个线 ...
- Qt之线程同步(生产者消费者模式 - QSemaphore)
简述 生产者将数据写入缓冲区,直到它到达缓冲区的末尾,此时,它将从开始位置重新启动,覆盖现有数据.消费者线程读取数据并将其写入标准错误. Semaphore(信号量) 比 mutex(互斥量)有 ...
- Qt之线程同步(生产者消费者模式 - QWaitCondition)
简述 生产者将数据写入缓冲区,直到它到达缓冲区的末尾,这时,它从开始位置重新启动,覆盖现有数据.消费者线程读取数据并将其写入标准错误. Wait condition(等待条件)比单独使用 mut ...
- linux下线程池实现
linux下线程池实现 转自:http://blog.csdn.net/lmh12506/article/details/7753952 前段时间在github上开了个库,准备实现自己的线程池的,因为 ...
- 在Linux系统下生产者消费者,Linux线程编程之生产者消费者问题
前言 本文基于顺序循环队列,给出Linux生产者/消费者问题的多线程示例,并讨论编程时需要注意的事项.文中涉及的代码运行环境如下: 本文假定读者已具备线程同步的基础知识. 一 顺序表循环队列 1.1 ...
- 【Linux入门】多线程(线程概念、生产者消费者模型、消息队列、线程池)万字解说
目录 1️⃣线程概念 什么是线程 线程的优点 线程的缺点 线程异常 线程异常 Linux进程VS线程 2️⃣线程控制 创建线程 获取线程的id 线程终止 等待线程 线程分离 3️⃣线程互斥 进程线程间 ...
最新文章
- 一种电磁铁磁场分析测量
- Java Spring Boot VS .NetCore (七) 配置文件
- 解决修改“文件夹选项”后仍不能显示隐藏文件一例
- seir模型启发式算法_好文荐读|选址问题的启发式方法
- spring循环依赖 第二讲
- matlab在神经网络中的应用,应用matlab实现神经网络
- java限制输入字符长度_input限制输入长度
- 阿里云域名注册+服务器购买+备案教程(图文教程)
- 在ArcMap中制作Python加载项点击小班依次编号
- Win7环境下硬盘安装XP(无光驱/光盘安装XP)
- android apk安装工具,安卓装机必备工具!一键批量安装应用apk
- 2018_WWW_DKN- Deep Knowledge-Aware Network for News Recommendation阅读笔记
- 【原型模式】原型模式深入分析
- 轻松在Google Chrome浏览器中管理您的电子邮件
- oracle数据库恢复aul_AUL6数据恢复总结_20140404
- MySQL获取 查询上周的周一 查询上周的周日(星期日)查询本周的周一(星期一) 查询本周的周日(星期日)
- 精算与金融建模行业解决方案白皮书,不要错过!
- 基于PyQt5实现界面控件自适应大小
- 高清监控视频存储最佳方案
- 管理经济学【八】之 完全竞争市场中的企业决策
热门文章
- 摄影基础知识——焦点
- Python搭建BT资源搜索站
- 《Python与硬件项目案例》— 基于Python与指纹模块AS608的指纹识别签到考勤系统(上篇)(期末大作业、课程设计、毕业设计、结课项目)
- 超简单的用PS(PhotoShop)转换png为ico,简单的制作favicon.ico,使用画图工具转换PNG为ICO图标,不用下什么插件软件什么玩意儿的
- win10升级后 IE 火狐 谷歌浏览器 不能上网 解决办法
- 李宏毅2021机器学习笔记——Transformer
- 计算机主板大小性能区别,电脑主板是大板好还是小板好 主板中大板和小板的区别介绍...
- 执着女股民:20年炒一只股票 翻几十倍很轻松
- 架构师须知概念:POJO、PO、DTO、DAO、BO、VO
- GAMIT基线解算(批处理步骤)