线程经典问题

本月任务
一. 解决生产者消费者问题
二. 解决哲学家吃饭问题
三. 实现进程池/线程池

在编译运行程序之后需要加上 -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只才能吃饭,并且一次只能拿起身边的一支筷子。一旦拿起便不会放下筷子直到把饭吃完,此时才把这双筷子放回原处。如果,很不幸地,每个哲学家拿起他或她左边的筷子,那么就没有人可以吃到饭了。

哲学家进餐问题是一个多线程运用的经典例子,涉及到线程同步/互斥临界区访问问题以及死锁问题

吃饭时使用筷子:

  1. 拿一双筷子才能吃
  2. 每次只允许拿一支筷子
  3. 只能拿身边的筷子
  4. 吃完才放下筷子

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下线程经典问题(生产者消费者问题,哲学家问题...)相关推荐

  1. Linux下实现多线程的生产者消费者问题

    Linux下实现多线程的生产者消费者问题 一.原理的理解 生产者-消费者问题是一个经典的线程同步问题,该问题最早由Dijkstra提出,用以演示他提出的信号量机制.在同一个线程地址空间内执行的两个线程 ...

  2. 生产者消费者_【线程通信】生产者消费者模型

    1生产者消费者模型介绍 生产者消费者模型,是每一个学习多线程的的人都需要知道的模型; 大致情况就是:有两个线程,一个负责生产产品,一个消费产品,两者公用同一块内存区域,也就是产品放在了同一块内存上面, ...

  3. linux下线程错误码表

    linux下线程错误码在/usr/include/asm-generic/errno-base.h中查看

  4. 线程通信之生产者消费者阻塞队列版

    线程通信之生产者消费者阻塞队列版 ProdConsumer_BlockQueueDemo.java import java.util.concurrent.ArrayBlockingQueue; im ...

  5. Java多线程之线程通信之生产者消费者阻塞队列版

    Java多线程之线程通信之生产者消费者传统版和阻塞队列版 目录 线程通信之生产者消费者传统版 线程通信之生产者消费者阻塞队列版 1. 线程通信之生产者消费者传统版 题目: 一个初始值为零的变量,两个线 ...

  6. Qt之线程同步(生产者消费者模式 - QSemaphore)

     简述 生产者将数据写入缓冲区,直到它到达缓冲区的末尾,此时,它将从开始位置重新启动,覆盖现有数据.消费者线程读取数据并将其写入标准错误. Semaphore(信号量) 比 mutex(互斥量)有 ...

  7. Qt之线程同步(生产者消费者模式 - QWaitCondition)

     简述 生产者将数据写入缓冲区,直到它到达缓冲区的末尾,这时,它从开始位置重新启动,覆盖现有数据.消费者线程读取数据并将其写入标准错误. Wait condition(等待条件)比单独使用 mut ...

  8. linux下线程池实现

    linux下线程池实现 转自:http://blog.csdn.net/lmh12506/article/details/7753952 前段时间在github上开了个库,准备实现自己的线程池的,因为 ...

  9. 在Linux系统下生产者消费者,Linux线程编程之生产者消费者问题

    前言 本文基于顺序循环队列,给出Linux生产者/消费者问题的多线程示例,并讨论编程时需要注意的事项.文中涉及的代码运行环境如下: 本文假定读者已具备线程同步的基础知识. 一  顺序表循环队列 1.1 ...

  10. 【Linux入门】多线程(线程概念、生产者消费者模型、消息队列、线程池)万字解说

    目录 1️⃣线程概念 什么是线程 线程的优点 线程的缺点 线程异常 线程异常 Linux进程VS线程 2️⃣线程控制 创建线程 获取线程的id 线程终止 等待线程 线程分离 3️⃣线程互斥 进程线程间 ...

最新文章

  1. 基于光照的物理模型(一)
  2. [HNOI2016] 大数(莫队)
  3. 网线重新插拔后恢复正常_生活小窍门之网线不够长怎么办,毕亚兹网线连接器轻松搞定...
  4. 面试题,产品方案开发说实现不了,作为产品经理你该怎么办?
  5. 《Android开发从零开始》——26.数据存储(5)
  6. powershell快捷键_关于powershell的知识你知道多少呢
  7. 关于Redis命令keys在性能方面的说明
  8. 计算机类专业工程认证,计算机类专业工程教育专业认证补充标准
  9. ASP.NET验证码
  10. velocity语法教程
  11. matlab 符号函数 计算,MATLAB符号计算函数用法总结
  12. 推荐一款Mac远程桌面工具——Parallels Client(免费)
  13. 测试质量体系搭建--测试团队目标
  14. 计算机一级学科大学排名,大学计算机排名(一级学科)
  15. 上传文件到本地操作和上传到Azure云上
  16. vue4 跳转外部链接_vue项目跳转到外部链接
  17. 程序员与颈椎病(三):颈椎病终极解决办法
  18. 每日随笔:笔记本WiFi突然连接不上重启才好的问题解决方式
  19. win10升级nvidia、cuda、cudnn,非常简单
  20. RNA 10. SCI 文章中基因表达富集之 KEGG 注释

热门文章

  1. VS中读取NMEA数据进行定位精度分析
  2. phpcms 允许英文目录有空格
  3. PS指正火焰文字特效
  4. MySQL 事务四大特性和事务隔离级别
  5. java中POJO、PO、BO、VO、DTO和DAO的概念
  6. 2.sklearn—评价指标大全(平均误差、均方误差、混淆矩阵、准确率、查全率、查准率、召回率、特异度,F1-score、G-mean、KS值、ROC曲线、AUC值、损失函数、结构风险最小)
  7. 嫦娥五号顺利升空,NASA、欧洲航天局回应
  8. ASP.NET Word转换成PDF文件
  9. 实例详解——编译器命令#pragma section作用于函数时作用域是否覆盖到其子函数...
  10. ClickHouse常用函数统计