Linux——多线程下解决生产消费者模型
我们学习了操作系统,想必对生产消费者问题都不陌生。作为同步互斥问题的一个经典案例,生产消费者模型其实是解决实际问题的基础模型,解决很多的实际问题都会依赖于它。而此模型要解决最大的问题便是同步与互斥。而通常呢,在多进程的环境下我们一般是是用信号量来解决(可以戳这里看看);在多线程的情况,则会用到两个东西: 互斥量和条件变量。通常用它们两个来实现线程间通信,以此来解决多线程下的同步和互斥问题。不过在具体实现生产消费模型前,为了更好理解当中的处理原理,还是先来回顾一下一些线程间通信的相关知识。
互斥问题
大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。多个线程并发的操作共享变量,会带来一些问题。
// 操作共享变量会有问题的售票系统代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int ticket = 100;
void *route( void *arg)
{
char id = *(char*)arg;
while ( 1 ) {
if ( ticket > 0 ) {
usleep( 1000) ;
printf( " thread %c sells ticket:%d\n" , id, ticket) ;
ticket--;
} else {
break;
}
}
}
int main(void)
{
pthread_t t1, t2, t3, t4;
char a1=1,a2=2,a3=3,a4=4;
pthread_create( &t1, NULL, route, &a1);
pthread_create( &t2, NULL, route, &a2);
pthread_create( &t3, NULL, route, &a3);
pthread_create( &t4, NULL, route, &a4);
pthread_join( t1, NULL) ;
pthread_join( t2, NULL) ;
pthread_join( t3, NULL) ;
pthread_join( t4, NULL) ;
}
//一次执行结果:
thread sells ticket:
...
thread sells ticket:
thread sells ticket:
thread sells ticket:-
thread sells ticket:-
为什么无法获得正确结果?
·if 语句判断条件为真以后,代码可以并发的切换到其他线程;usleep这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
·ticket--操作本身就不是一个原子操作
如果取出 “ticket--”部分的汇编代码
objdump -d a.out > test.objdump
40064b: 8b e3 mov 0x2004e3(%rip),%eax # 600b34
<ticket>
: e8 sub $0x1,%eax
: da mov %eax,0x2004da(%rip) # 600b34
ticket--操作并不是原⼦子操作,而是对应三条汇编指令:
load:将共享变量ticket从内存加载到寄存器中
update: 更新寄存器里面的值,执行-1操作
store:将新值,从寄存器写回共享变量ticket的内存地址
要解决以上问题,需要做到三点:
1.代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
2.如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
3.如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。
Linux下用互斥量就做到了以上3点,它本质上其实就是一把锁。
互斥量
互斥量使用一般是以下几个步骤:
1.定义互斥量(mutex): pthread_mutex_t mutex;
2.初始化:
①静态分配:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
②动态分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数:
mutex:要初始化的互斥量
attr:如果不设置线程属性的话填NULL
3.上锁:pthread_mutex_lock(&mutex) 如果是1,值0,返回; 如果是0,便阻塞
调⽤用pthread_ lock 时,可能会遇到以下情况:
互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,
但没有竞争到互斥量,那么pthread_ lock调⽤用会陷入阻塞,等待互斥量解锁。
4.解锁: pthread_mutex_unlock(&mutex) 置为1,返回
5.销毁:pthread_mutex_destroy(&mutex)
销毁互斥量需要注意:使⽤用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁
不要销毁⼀一个已经加锁的互斥量已经销毁的互斥量,要确保后⾯面不会有线程再尝试加锁
自旋锁
互斥锁是当阻塞在pthread_mutex_lock时,放弃CPU,好让别人使用CPU。自旋锁阻塞在spin_lock时不会阻塞CPU,不断对CPU询问。(实时系统中应用比较多,要求对锁进行较快响应)它使用形式与互斥量类似,不再赘述。
.定义自旋锁: pthread_spinlock_t spin .初始化自旋锁:pthread_spin_intt(pthread_spinlock_t *s, int s) .上锁:int pthread_spin_lock(pthread_spinlock_t *lock) .解锁:int pthread_spin_lock(pthread_spinlock_t *lock) .销毁:int pthread_spin_lock(pthread_spinlock_t *lock)
读写锁
在编写多线程的时候,有⼀一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极⼤大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。读写锁本质上是一种自旋锁[长时间等人和短时间等人的例子]
·注意:读共享,写排他,写优先级高
它处理方式和前面互斥量类似,就不在赘述。
.定义:pthread_rwlock_t lock .初始化 pthread_rwlock_init(&lock, NULL) .上锁:pthread_rwlock_rdlock(&lock) pthread_rwlock_wrlock(&lock) .解锁:pthread_rwlock_unlock(&lock) .销毁:pthread_rwlock_destroy(&lock)
条件变量
生产者消费问题要解决另一个问题就同步的问题。当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。例如一个线程访问队列时,发现队列为空,它只能等待,直到其它线程将一个节点添加到队列中。这种情况下就需要用到条件变量了。
使用步骤如下:
1.定义条件变量:
pthread_cond_t cond;
pthread_mutex_t mutex
2.初始化条件变量:
pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *restrict attr)
参数:
cond:要初始化的条件变量
attr:填NULL(用于设置线程属性)
3.等待条件:
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,后⾯面详细解释
注意这里wait函数需要互斥量(后面解释)。如果在锁环境下,此处互斥量形同虚设。在锁环境下,会将mutex解锁; wait返回时,将mutex锁制成原来状态
4.使条件满足 :
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
5.销毁条件变量:
int pthread_cond_destroy(pthread_cond_t *cond)
为什么pthread_ cond_ wait 需要互斥量?
①条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
②条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。
按照上面的说法,我们设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上不就行了?但是这样也会有问题
// 错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false) {
pthread_mutex_unlock(&mutex);
//解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过
pthread_cond_wait(&cond);
pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);
·由于解锁和等待不是原子操作。调用解锁之后,pthread_cond_wait之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么pthread_ cond_wait将错过这个信号,可能会导致线程永远阻塞在这个pthread_cond_wait。所以解锁和等待必须是一个原子操作。
·int
pthread_cond_wait;
进入该函数后,会去看条件量是否为0?等于0,就把互斥量变成1,直到cond_wait返回时,把条件量改成1,同时将互斥量恢复成原样。
所以正确是条件变量的使用规范是这样的:(这里以生产消费问题为例 简单的实现一下同步,使得消费者需要在有产品的情况下才可进行消费。)
条件变量使用范例即:
·等待条件:
pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(&cond, &mutex);
//pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mutex,
//然后阻塞在等待队列里休眠,直到再次被唤醒
//(大多数情况下是等待的条件成立而被唤醒,唤醒后,
//该进程会进行pthread_mutex_lock(&mutex)先锁定,然后再读取资源
修改条件
pthread_mutex_unlock(&mutex);
·给条件发送信号代码
pthread_mutex_lock(&mutex);
//设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
多线程下的生产消费者问题
好,这下终于把准备工作做好了,结合线程的基本操作,多线程下的生产消费者我们也就不难实现出来了。如下:
/*************************************************************************
> File Name: pc.c
> Author: tp
> Mail:
> Created Time: Sun 27 May 2018 06:28:33 PM CST
************************************************************************/ #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h> #define PRO_NUM 3 //生产线程数量
#define CON_NUM 0 //消费线程数量
pthread_cond_t cond;
pthread_cond_t n_empty;
pthread_mutex_t mutex; int g_num = 0; //产品数量
int empty_num = 3; //生产的空位数量 //productor
void* pro_route(void* arg)
{
int id =*(int*)arg;
free(arg); while(1)
{
pthread_mutex_lock(&mutex);
while(empty_num <= 0)
{
printf("生产线程%d等待。\n", id);
pthread_cond_wait(&n_empty, &mutex);
printf("有空位,数量为%d\n", empty_num);
}
printf("生产线程%d生产\n", id);
++g_num;
--empty_num;
printf("生产品%d完成\n", g_num);
sleep(rand()%3);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
sleep(rand()%3);
}
}
//consumer
void *con_route(void* arg)
{
int id =*(int*)arg;
free( arg); while(1)
{
pthread_mutex_lock(&mutex);
while(g_num <= 0)
{
printf("消费线程%d等待。。\n", id);
pthread_cond_wait(&cond, &mutex);
printf("第%d产品到了!!\n", g_num);
}
printf("消费线程%d消费 产品%d\n", id , g_num);
--g_num;
++empty_num;
sleep(rand()%2);
printf("消费线程%d消费完成\n", id);
pthread_cond_signal(&n_empty);
pthread_mutex_unlock(&mutex);
sleep(rand()%3);
}
}
int main( )
{
srand(getpid()); pthread_t tids[PRO_NUM + CON_NUM];
//互斥量,条件变量初始化
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL); //条件变量1
pthread_cond_init(&n_empty, NULL);//条件变量2 //创建生产者线程
for(int i =0; i< PRO_NUM; ++i)
{
int* p = (int*)malloc(sizeof(int)); //传入参数相当作线程编号
*p = i;
pthread_create(&tids[i], NULL, pro_route, p);
}
//创建消费者线程
for(int i =0; i< CON_NUM; ++i)
{
int* p = (int*)malloc(sizeof(int)); //消费线程编号
*p = i;
pthread_create(&tids[i], NULL, con_route, p);
}
for(int i =0; i< PRO_NUM + CON_NUM; ++i) //回收线程
{
pthread_join(tids[i], NULL);
}
//互斥量,条件变量销毁
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
pthread_cond_destroy(&n_empty); return 0;
}
结果:
当我们只有生产者生产时,此时生产的空位生产满了之后便会阻塞,如下:
当去添加两个消费线程时(将CON_NUM改为2),这样生产、消费得以进行。如下:
Linux——多线程下解决生产消费者模型的更多相关文章
- java线程基础巩固---多线程下的生产者消费者模型,以及详细介绍notifyAll方法
在上一次[http://www.cnblogs.com/webor2006/p/8419565.html]中演示了多Product多Consumer假死的情况,这次解决假死的情况来实现一个真正的多线程 ...
- Python并发编程04 /多线程、生产消费者模型、线程进程对比、线程的方法、线程join、守护线程、线程互斥锁
Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线程join.守护线程.线程互斥锁 目录 Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线 ...
- Java多线程学习笔记--生产消费者模式
实际开发中,我们经常会接触到生产消费者模型,如:Android的Looper相应handler处理UI操作,Socket通信的响应过程.数据缓冲区在文件读写应用等.强大的模型框架,鉴于本人水平有限目前 ...
- Kafka下的生产消费者模式与订阅发布模式
原文:https://blog.csdn.net/zwgdft/article/details/54633105 在RabbitMQ下的生产消费者模式与订阅发布模式一文中,笔者以“数据接入”和“事 ...
- Java生产消费者模型——代码解析
我们将生产者.消费者.库存.和调用线程的主函数分别写进四个类中,通过抢夺非线程安全的数据集合来直观的表达在进行生产消费者模型的过程中可能出现的问题与解决办法. 我们假设有一个生产者,两个消费者来共同抢 ...
- Python进阶----进程之间通信(互斥锁,队列(参数:timeout和block),), ***生产消费者模型
Python进阶----进程之间通信(互斥锁,队列(参数:timeout和block),), ***生产消费者模型 一丶互斥锁 含义: 每个对象都对应于一个可称为" 互斥锁&qu ...
- Python之queue模块以及生产消费者模型
队列 队列类似于一条管道,元素先进先出,进put(arg),取get() 有一点需要注意的是:队列都是在内存中操作,进程退出,队列清空,另外,队列也是一个阻塞的形态. 队列分类 队列有很多中,但都依赖 ...
- Python - Asyncio模块实现的生产消费者模型
[原创]转载请注明作者Johnthegreat和本文链接 在设计模式中,生产消费者模型占有非常重要的地位,这个模型在现实世界中也有很多有意思的对应场景,比如做包子的人和吃包子的人,当两者速度不匹配时, ...
- Linux系统下 解决Qt5无法连接MySQL数据库的方法
Linux平台下解决Qt5连接mysql数据库的问题:输入sudo apt-get install libqt5sql5-mysql解决,这种方法只能解决Qt是用sudo apt-get instal ...
随机推荐
- 一个ssm综合小案例-商品订单管理----写在前面
学习了这么久,一直都是零零散散的,没有把知识串联起来综合运用一番 比如拦截器,全局异常处理,json 交互,RESTful 等,这些常见技术必须要掌握 接下来呢,我就打算通过这么一个综合案例把这段时间 ...
- Docker 入门 第一部分: 定位和设置
目录 Docker 入门 第一部分: 定位和设置 Docker概念 镜像和容器 容器和虚拟机 准备你的Docker环境 测试 Docker 的版本 测试 Docker 安装 回顾 总结 Docker ...
- 《Two Dozen Short Lessons in Haskell》(二十四)代数类型
这是<Two Dozen Short Lessons in Haskell>这本书的最后一章,第23章没有习题. 这一章里介绍了Haskell如果自定义一种类型,并且用一个双人博弈游戏为例 ...
- AngularJS -- 代码实例
整理书籍内容(QQ:283125476 发布者:M [重在分享,有建议请联系->QQ号]) ng-change 当文本输入字段中内容发生了变化,就会改变equation.x的值: <bod ...
- ZYNQ. Interrupt(2)SPI.AXI TIMER
Shared Peripheral Interrupts (SPI) SPI 可以接收来自PL的中断,这里使用PL模块 AXI Timer 的中断模式,并连接到CPU. AXI TIMER 定时器,内 ...
- pytorch之LSTM
from:http://pytorch-cn.readthedocs.io/zh/latest/package_references/torch-nn/#recurrent-layers class ...
- 【Python】HackBack(获取暴力破解服务器密码的IP来源)
1.前言 又在0x00sec上翻到好东东. https://0x00sec.org/t/python-hackback-updated/882 帖子里的脚本会得到那些暴力服务器密码失败的IP和用户名, ...
- Ansible Tower系列 四(使用tower执行一个命令)【转】
在主机清单页面中,选择一个主机清单,进入后,选择hosts里的主机 Paste_Image.png 点击 RUN COMMANDS MODULE 选择 commandARGUMENTS 填写 ifco ...
- [转] caffe数据层参数说明
原文地址:http://www.cnblogs.com/denny402/p/5070928.html 稍有修改: 数据层是每个模型的最底层,是模型的入口,不仅提供数据的输入,也提供数据从Blobs转 ...
- NOIp 2018 普及组
T1标题统计 传送门 题目描述 凯凯刚写了一篇美妙的作文,请问这篇作文的标题中有多少个字符? 注意:标题中可能包含大.小写英文字母.数字字符.空格和换行符.统计标题字 符数时,空格和换行符不计算在内. ...