【Linux】生产者与消费者模型、信号量、死锁
目录
死锁
死锁的产生场景
死锁的gdb分析
1、通过调试可执行程序来分析
2、通过调试正在运行的程序
死锁的必要条件
死锁的预防
生产者与消费者模型
123规则
应用场景及特点
代码实现:
信号量
原理:
接口:
初始化接口:
等待接口:
释放接口:
销毁接口:
注意事项:
1、对于生产者与消费者来说,获取信号量与加锁的先后顺序是怎样的?
2、信号量既可以保证同步,也可以保证互斥
使用信号量来实现生产者与消费者模型
死锁
死锁的产生场景
1、线程加锁之后,没有释放互斥锁就退出了
因此,要在线程所有可能退出的地方都释放互斥锁。
2、两种线程分别拿着一把锁,还想要请求对方的锁
死锁的gdb分析
产生了死锁,要分析其产生的原因,可以通过gdb来调试该程序,进而判断思索地原因。下面以上述情况2进行调试。
1、通过调试可执行程序来分析
1、b + 行号 打断点
2、使用thread apply all bt命令将所有线程的调用堆栈展现出来
3、使用p + 互斥锁变量可以查看互斥锁的内容
2、通过调试正在运行的程序
gdb attack + 进程号
此时就可以进入gdb调试当中,我们可以按照上述查看线程调用堆栈以及查看互斥锁的内同等命令来分析我们的代码。
死锁的必要条件
1、不可剥夺
线程获取到互斥锁后,除了自己释放,其他线程不能进行释放
2、循环等待
线程A拿着1锁请求2锁,线程B拿着2锁请求1锁
3、互斥条件
一个互斥锁在同一时间只能被一个线程拥有
4、请求与保持
吃着碗里的,看着锅里的,其实就是死锁产生的情况2那种情形
死锁的预防
1、破环必要条件
- 破坏循环等待
- 破坏请求与保持
- 剩下的不可剥夺和互斥条件是锁的固有属性,无法破坏
2、加锁顺序一致
都先加同一把锁,再去加另一把锁
3、避免锁没有被释放
在线程所有退出的地方都进行解锁
4、资源一次性分配
多个资源在代码当中有可能每一个资源都需要使用不同的锁进行保护,比如:资源A需要1锁,资源B需要2锁,就有可能出现多个线程在使用这两个资源的时候,出现循环等待的情况。
比如可以让多个资源共同使用同一把锁,就能达到一次性分配的目的。
生产者与消费者模型
123规则
1个线程安全的队列:保证先进先出的数据结构即可,互斥 + 同步
2种角色的线程:生产者和消费者
3个规则:
- 生产者与生产者之间互斥
- 消费者与消费者之间互斥
- 生产者与消费者互斥 + 同步
应用场景及特点
一般应用于后端程序当中,比如微信的后端程序:
1、接受消息的线程接受消息放到队列中
2、多个线程充当消费者从队列中读取数据并处理3、这多个线程又充当生产者,将处理完毕的结果放到另一个队列中。
4、发送数据的线程从队列中拿数据并发送
优点:
1、忙闲不均
2、生产者与消费者高度解耦
3、支持高并发
代码实现:
1 #include<stdio.h> 2 #include<queue>3 using namespace std;4 #include<pthread.h>5 #include<unistd.h>6 7 #define THREAD_COUNT 28 //线程安全队列9 class RingQueue{10 public:11 RingQueue(){12 capacity = 10;13 pthread_mutex_init(&_que_lock, NULL);14 pthread_cond_init(&_cons_cond, NULL);15 pthread_cond_init(&_prod_cond, NULL);16 }17 ~RingQueue(){18 pthread_mutex_destroy(&_que_lock);19 pthread_cond_destroy(&_cons_cond);20 pthread_cond_destroy(&_prod_cond);21 }22 //提供给生产者线程使用的接口23 void push(int data){24 pthread_mutex_lock(&_que_lock);25 while(_que.size() == capacity){26 pthread_cond_wait(&_prod_cond, &_que_lock);27 }28 _que.push(data);
W> 29 printf("I am produce pthread %p: I produce %d\n", pthread_self(), data);30 pthread_mutex_unlock(&_que_lock);31 //通知消费者消费32 pthread_cond_signal(&_cons_cond);33 }34 //提供给消费者线程进行消费的线程35 void pop(int* data){36 pthread_mutex_lock(&_que_lock);37 while(_que.size() == 0){38 pthread_cond_wait(&_cons_cond, &_que_lock);39 }40 *data = _que.front();41 _que.pop();
W> 42 printf("I am consume thread %p : I consume %d\n", pthread_self(), *data);43 pthread_mutex_unlock(&_que_lock);44 //通知生产者线程生产45 pthread_cond_signal(&_prod_cond);46 }47 private:48 queue<int> _que;49 size_t capacity;50 //互斥锁51 pthread_mutex_t _que_lock;52 //同步53 pthread_cond_t _cons_cond;54 pthread_cond_t _prod_cond;55 };56 57 int g_data = 0;//注意:静态初始化互斥锁保证临界区资源代码的原子性//不能在该函数内部定义互斥锁变量,因为那样多个生产者拿到的不是一个互斥锁,会造成程序结果 //的二义性58 pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;59 void* pro_thread_start(void* arg){60 RingQueue* rq = (RingQueue*)arg;61 while(1){62 pthread_mutex_lock(&g_lock);63 rq->push(g_data);64 g_data++;65 //sleep(1);66 pthread_mutex_unlock(&g_lock);67 }68 }69 void* con_thread_start(void* arg){70 RingQueue* rq = (RingQueue*)arg;71 while(1){72 int data;73 rq->pop(&data);74 }75 }76 int main(){77 RingQueue* que = new RingQueue();78 pthread_t pro[THREAD_COUNT], con[THREAD_COUNT];79 for(int i = 0; i < THREAD_COUNT; i++){80 int ret = pthread_create(&pro[i], NULL, pro_thread_start, que);81 if(ret < 0){82 perror("pthread_create");83 }84 ret = pthread_create(&con[i], NULL, con_thread_start, que);85 if(ret < 0){86 perror("pthread_create");87 88 }89 }90 //主线程等待回收工作线程91 for(int i = 0; i < THREAD_COUNT; i++){92 pthread_join(pro[i], NULL);93 pthread_join(con[i], NULL);94 }95 delete que;96 return 0;97 }
结果:
信号量
原理:
信号量是由一个资源计数器和一个PCB等待队列构成。
PCB等待队列:与条件变量实现同步中的PCB等待队列是一样的。
资源技术器:
执行流获取信号量:
获取成功:资源计数器减一
获取失败:执行流被放到PCB等待队列中去
执行流在释放信号量后,对资源计数器进行+1操作。
生产者信号量初始化时资源资源计数器的值一般为线程安全队列的容量大小。消费者信号量初始化时资源计数器的值一般为,因为刚开始,线程安全队列中并没有资源可以使用。
接口:
初始化接口:
sem:信号量,sem_t是信号量的类型
pshared: 表示该信号量用途的标识符;0用于线程间,是一个全局性质的结构体变量;非0用于进程间,涉及到进程间通信,该变量肯定存在每个进程都能访问到的地方,比如共享内存。
value:资源的个数,用来初始化信号量的资源计数器
等待接口:
1、对资源计数器进行-1操作
2、判断资源计数器的值是否小于0
小于0:阻塞等待,将执行流放到PCB等待队列中
不小于0:接口返回
释放接口:
1、对资源计数器+1操作
2、判断资源计数器的值是否小于等于0
是:通知PCB等待队列(说明有其他线程在PCB等待队列中)
否:不同通知PCB等待队列因为没有线程在等待
销毁接口:
int sem_destroy(sem_t* sem)
信号量是动态初始化的,因此需要销毁
注意事项:
1、对于生产者与消费者来说,获取信号量与加锁的先后顺序是怎样的?
场景:有一个容量为2的线程安全队列,有3个生产者线程ABC,生产者对应的信号量中资源计数器初始值为2,有一个消费者,消费者资源计数器初始值为0。
场景一:先拿互斥锁,再获取信号量
假设生产者线程A先拿到互斥锁 ,然后去获取信号量,并对生产者的资源计数器-1,此时值变为1;然后线程A去访问线程安全队列这一临界资源,访问完毕后,线程A释放信号量,并对消费者信号量的资源计数器+1,此时消费者资源计数器为1。
假设接下来又是生产者线程B拿到互斥锁,然后去获取信号量,此时生产者资源计数器变为0,经过一系列操作后,消费者资源计数器的值变为2。
此时,线程安全队列中已经没有空闲空间可以使用了。
假设生产者C又拿到了互斥锁,然后去获取信号量,并对生产者的资源计数器-1,现在资源计数器小于0.因此阻塞等待,将线程C放到生产者的PCB等待队列中去。
此时,线程C带着互斥锁进到了PCB等待队列等待其他线程的唤醒。但是其他线程并不能获取到互斥锁,因而也就无法唤醒该线程。整个线程处于卡死状态,无法继续向下执行。
因此这种情况是错误的。
场景二:先获取信号量,再获取互斥锁
对于信号量的获取,线程ABC是抢占式获取的,再获取到信号量修改计数器是原子性的。
假设AB先获取到信号量并对资源计数器减一,此时计数器的值为0。对于获取到信号量的AB,假设线程A先获取到互斥锁。然后访问临界资源,释放信号量,对消费者资源计数器+1。
假设接下来又是生产者线程B拿到互斥锁,此时整个线程安全队列已经放满。
接下来由消费者线程获取到互斥锁,然后消费者线程再对临界区访问完毕后,释放信号量,对生产者的资源计数器+1,发现值小于等于0,因此会通知生产者的PCB等待队列,然后线程就去直接获取互斥锁。
因此是先获取信号量,再保证互斥(方式:互斥锁 || 信号量)
2、信号量既可以保证同步,也可以保证互斥
信号量保证同步,再分析上一个问题的时候已经体现出来了。
而保证互斥只需要将信号量中资源计数器的初始值设置为1,就能够保证互斥了。
将资源计数器的值设置为1,也就意味着只有一个线程能够获取到信号量,其他线程再获取的时候,资源计数器的值小于0,他们都会被放到该信号量的PCB等待队列中去;只有等到获取到信号量的那个线程访问临界资源完成,将该信号量释放后,其他线程才能够获取到该信号量,进而访问临界资源。
使用信号量来实现生产者与消费者模型
使用一个环形队列作为线程安全队列,该环形队列使用一个数组来模拟:
1 #include<stdio.h>2 #include<unistd.h>3 #include<pthread.h>4 #include<vector>5 #include<semaphore.h>6 7 using namespace std;8 /*9 * 定义线程安全的队列10 * 环形队列(用数组模拟)11 * 线程安全:12 * 同步:信号量13 * 互斥: 信号量14 * */15 #define CAPACITY 216 #define THREAD_COUNT 217 18 class RingQueue{19 public:20 RingQueue()21 :_vec(CAPACITY){ 22 _capacity = CAPACITY;23 sem_init(&_sem_lock, 0, 1);24 sem_init(&_cons_sem, 0, 0);25 sem_init(&_prod_sem, 0, CAPACITY);26 write_pos = 0;27 read_pos = 0;28 }29 ~RingQueue(){30 sem_destroy(&_sem_lock);31 sem_destroy(&_cons_sem);32 sem_destroy(&_prod_sem);33 }34 void push(int data){35 //获取生产者信号量36 sem_wait(&_prod_sem);37 //获取互斥锁38 sem_wait(&_sem_lock);
W> 39 printf("I am produce thread %p: I produce %d\n", pthread_self(), data);40 _vec[write_pos] = data;41 write_pos = (write_pos + 1) % _capacity;42 sem_post(&_sem_lock);43 //通知消费者进行消费44 sem_post(&_cons_sem);45 }46 void pop(){47 //获取消费者信号量48 sem_wait(&_cons_sem);49 sem_wait(&_sem_lock);50 int data = _vec[read_pos];
W> 51 printf("I am consum thread %p: I consum %d\n", pthread_self(), data);52 read_pos = (read_pos + 1) % _capacity;53 sem_post(&_sem_lock);54 //通知生产者生产55 sem_post(&_prod_sem);56 }57 private:58 vector<int> _vec;59 size_t _capacity;60 //保证互斥的信号量61 sem_t _sem_lock;62 sem_t _cons_sem; //消费者的信号量63 sem_t _prod_sem; //生产者的信号量64 65 int write_pos;66 int read_pos;67 };68 int g_data = 0;69 sem_t g_lock;70 void* prod_thread_start(void* arg){71 RingQueue* rq = (RingQueue*)arg;72 while(1){73 sem_wait(&g_lock);74 rq->push(g_data);75 g_data++;76 sem_post(&g_lock);77 }78 }79 void* cons_thread_start(void* arg){80 RingQueue* rq = (RingQueue*)arg;81 while(1){82 rq->pop();83 }84 }85 int main(){86 RingQueue* rq = new RingQueue();87 //保证多个生产者互斥访问88 sem_init(&g_lock, 0, 1);89 90 pthread_t cons[THREAD_COUNT], prod[THREAD_COUNT];91 for(int i = 0; i < THREAD_COUNT; ++i){92 int ret = pthread_create(&prod[i], NULL, prod_thread_start, (void*)rq);93 if(ret < 0){94 perror("pthread_create");95 }96 ret = pthread_create(&cons[i], NULL, cons_thread_start, (void*)rq);97 if(ret < 0){98 perror("pthread_create");99 }100 }101 for(int i = 0; i < THREAD_COUNT; ++i){102 pthread_join(prod[i], NULL);103 pthread_join(cons[i], NULL);104 }105 sem_destroy(&g_lock);106 delete rq;107 return 0;108 }
【Linux】生产者与消费者模型、信号量、死锁相关推荐
- Linux 生产者与消费者模型C++实现
生产者与消费者模型 本篇博客代码实现都是在linux环境下跑的 通过条件变量实现 应用场景:针对大量数据的产生与处理的场景 生产与处理是放到不同执行流中完成的,中间会增加一个数据缓冲区,作为中间的数据 ...
- Linux系统编程40:多线程之基于环形队列的生产者与消费者模型
文章目录 (1)什么是信号量 (2)与信号量相关的操作 (3)基于环形队列的生产者与消费者模型-信号量(单消费者单生产者) (1)什么是信号量 前面的叙述中,我们通过锁保证了每次只有一个线程进入临界区 ...
- linux进程间通信:system V 信号量 生产者和消费者模型编程案例
生产者和消费者模型: 有若干个缓冲区,生产者不断向里填数据,消费者不断从中取数据 两者不冲突的前提: 缓冲区有若干个,且是固定大小,生产者和消费者各有若干个 生产者向缓冲区中填数据前需要判断缓冲区是否 ...
- Linux系统编程:使用semaphore信号量和mutex互斥量实现多个生产者和消费者模型
代码实现 如题,使用semaphore信号量和mutex互斥量实现多个生产者和消费者模型.本来是想只用信号量实现生产者消费者模型的,但是发现 只能在一个生产者和一个消费者之间,要在多个生产者和消费者模 ...
- c/c++:线程同步(互斥锁、死锁、读写锁、条件变量、生产者和消费者模型、信号量)
目录 1. 概念 2. 互斥锁 3. 死锁 4. 读写锁 5. 条件变量 5.1 生产者和消费者模型 6. 信号量 1. 概念 线程同步: > 当有一个线程在对内存进行操作时,其他线程都不可以对 ...
- Linux系统编程---17(条件变量及其函数,生产者消费者条件变量模型,生产者与消费者模型(线程安全队列),条件变量优点,信号量及其主要函数,信号量与条件变量的区别,)
条件变量 条件变量本身不是锁!但它也可以造成线程阻塞.通常与互斥锁配合使用.给多线程提供一个会合的场所. 主要应用函数: pthread_cond_init 函数 pthread_cond_destr ...
- [Linux]生产者消费者模型(基于BlockQueue的生产者消费者模型 | 基于环形队列的生产者消费者模型 | 信号量 )
文章目录 生产者消费者模型 函数调用角度理解生产者消费者模型 生活角度理解生产者消费者模型 为什么要使用生产者消费者模型 生产者消费者模型优点 321原则 基于BlockingQueue的生产者消费者 ...
- python 进程间同步_python之路29 -- 多进程与进程同步(进程锁、信号量、事件)与进程间的通讯(队列和管道、生产者与消费者模型)与进程池...
所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了.至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠 ...
- 【Linux下】 线程同步 生产者与消费者模型
文章目录 [Linux下] 线程同步 生产者与消费者模型 线程同步 同步概念与竞态条件 条件变量 条件变量本质 操作条件变量 初始化和销毁条件变量 等待 唤醒 通过条件变量实现的简单线程同步例子 为什 ...
最新文章
- 工具用途_机械加工中研磨加工刀具(砂轮)﹑治工具及其用途
- 《当程序员的那些狗日日子》(五)工作中,工作外
- 黑马程序员 oc对象的方法成员变量
- Eureka常用配置详解
- 《java并发编程实践》笔记
- 一维数组和二维数组创建,输出,Arrays.fill()替换
- java future接口_java Future 接口介绍
- mysql 5.7日志配置_mysql-5.7日志设置
- asp.net ajax 1.0中detailview与updatepanel混合使用的例子
- 没有资本怎么创业的思维:不是钱,是实现。
- MySQL 复习笔记
- VS C++ memcpy() 用于double、int、结构体
- linux 中文输入鼠标跳动,解决wps for linux 中文输入法光标不跟随的问题
- MEGARAC(宝德)服务器BMC登录失败解决办法
- 基于网络安全的Docker逃逸
- 史上最长的介绍BI(商业智能)的干货
- 如何平衡新老策略的好与坏,一道常见风控送命题解答
- ASP实现在线发送邮件
- Explain how shipping point is determined?
- 手机卡,SIM卡,USIM 卡,nano卡
热门文章
- 苹果发布 3 款新 Mac ,首次搭载 ARM 架构芯片
- 阿里巴巴客户端开发工程师offer面经
- 新版 | 小O地图EXCEL版全新升级
- 免费资源分享(六) AAA级飞龙系列模型及动画
- c语言碾转相除法,从高中碾转相除法、更相减损术算法谈起
- 关于邮件系统被列入黑名单进行寄件的错误提示
- 几大知名电商网站的配送服务汇总
- 面对过年老家大姑的 “ 关心问候 ”,作为软件测试工程师的你如何解释
- 联想液晶显示器暴力拆解
- mysql distinct field_mysql – 如何提高COUNT(DISTINCT field1)的性能… GROUP BY field2?