您的位置:首页 > 运维架构 > Linux

Linux的多线程——生产者与消费者(条件变量、信号量)

2017-06-15 15:47 369 查看
在这之前的文章中,我简单介绍了线程(初识线程),同步与互斥以及互斥锁(同步与互斥)。现在我将在这篇文章中提到一种操作系统经常用到的模型——生产者与消费者模型。

什么是生产者与消费者模型?

在我看来,生产者与消费者模型是用来解决资源供求的问题,生产者与消费者其实就可以分别看做是资源的提供者与使用者。通过这个,我们可以分析出他们之间的关系:

①生产者与消费者之间是供求关系

②两个生产者之间是竞争关系(互斥)

③两个消费者之间也是竞争关系(互斥)

通过这个关系我们可以看到生产者与消费者之间需要进行资源的交易,生产者生产出来的资源要被消费者使用,那么就需要一个交易场所,也就是一个消费场所。

所以我们提供一个“321”原则用来记忆;3代表3种关系,2代表2个角色,1代表一个消费场所。

我这里给一个大概的结构图来解释生产者与消费者模型:



当然,为了不过于抽象,我们举一个寄信的例子:假设你需要寄一封信,那么你需要经历一下几个过程:

①你把信写好——相当于生产者生产数据;

②你把信放入邮筒——相当于生产者将数据放入到缓冲区

③邮递员把信从邮筒取出——相当于消费者将数据从缓冲区取出

④邮递员将信拿去邮局做出相应的处理——相当于消费者处理数据

那么,在上面的解释中,我们可以看到我是作为生产者去生产资源的,就是作为生产者;而邮筒就是作为缓冲区,即是消费场所;而邮递员就是消费者,是去消费资源的。

这样我们也可以解释上面的三种关系了,在生产者生产资源后消费者才去消耗资源,所以是供求关系;而生产者则是互相进行比较的,我生产的同时别人也在生产资源,所以是互相竞争的;同理,消费者也是这个理。

当然,在理解了这个同步与互斥之后,你是否想过,在邮递员跑过来之后,他看到你的邮筒是空的,是不是白跑了一趟,白费了力气。为了避免这样情况的发生,我们需要通过一种手段来帮助解决这种难题。当邮筒里有了信件之后,会通知邮递员“邮筒里有信了,你快来取走吧!”。其实这种手段我们叫做条件变量。

什么是条件变量?

条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:发送信号与等待信号。一般来说,条件变量与互斥锁是同步的,都是出自于Posix.1线程标准,多用于同步一个进程中的多个线程。怎么使用呢?当互斥锁用户上锁后,条件变量就用于等待;在一个进程或线程中调用pthread_cond_wait()来等待某个条件的成立,此时就阻塞在这里,另外一个进程或线程就进行某种操作,当某种条件成立时,调用pthread_cond_signal()来发送信号,从而使pthread_cond_wait()返回。注意了,这里的信号不是系统中的SIG类型的信号,我们将其看作信号量更为合适。

函数

所有的都包在#include<pthread.h>这个头文件里面,而且所有的条件变量的数据类型都是pthread_cond_t。

条件变量初始化

两种方法:

①直接定义一个全局的条件变量,然后利用宏PTHREAD_COND_INITIALIZER进行值的初始化;



②调用函数pthread_cond_init()函数



其中,第一个参数就是我们动态分配的条件变量的指针;第二个参数呢除非创建一个非默认属性的条件变量,否则始终为NULL;

注意:如不想将条件变量定义成全局,那么必须是动态分配的,而且必须先被destroy再被free。

就是这样定义:pthread_cond_t *cond = (pthread_cond_t*)malloc(sizeof(pthread_cond_t));

条件变量销毁

调用pthread_cond_destroy()



其中,参数就是条件变量的指针。

等待



我们使用pthread_cond_wait()或pthread_cond_timewait()函数等待条件变成真。

先解释wait吧:其中它的第一个参数还是指向条件变量的指针,第二个则是一个指向互斥锁的指针。在上面我曾提到条件变量与互斥锁一般来说是同步的,目的就是为了防止资源的竞争。其主要作用就是为了在消费者申请到锁,但是条件变量为假的时候,及时的释放锁资源。其实就是为了避免死锁(假设消费者A去商城购买物品,A拿到了锁的资源然后买走物品,在A买走了物品后释放锁的资源,B再拿到锁的资源去买,但是物品已经没了,此时条件变量就为假了,而B呢此时占据着锁的资源,不释放并且一直等待下去,直到有了新的物品,但是锁不被释放,生产者又申请不到锁资源去生产物品,消费者B还是在这等待,因此就产生了死锁)。

wait()是无条件等待,在条件变量为假的时候会一直等待下去,而timewait()则是有条件等待,它多定义了一个时间,超出这个时间就不再等待。这个值通过timespec来设定。

发送信号



在生产者生产完后去通知消费者进行消费,有两种通知方式。signal是根据优先级去唤醒一个等待者,而broadcast则是在资源充足的情况下进行广播,唤醒所有等待者。

条件变量与互斥锁搭配实现生产者与消费者模型。

首先,我写了一个链表作为消费场所

定义一个头文件:



然后再源文件中一个一个去实现这些函数



下面呢就是生产者与消费者的代码



然后我们去运行这部分的代码可以得到



每当生产者生产一个数据,消费者立刻接着消费一个数据。

上面我们实现了单个的生产者与消费者,每生产一个,那么就消费一个,那我们可不可以一次生产多个,然后再去进行消费呢?

其实是可以的,在这里我就需要提到信号量了。

什么是信号量?

它是一种专门用于提供不同进程之间或线程间同步的原语。即信号量是由内核进行管理的。

上面实现的是单个的生产与消费,那么我们用到了信号量就可以变成一次生产多个,然后再有消费者进行消费。

我们将之前讲到的商场消费想成多个,即有多个货架,多个货架就可以存放多个物品,可以循环利用这些货架,我们将其想象成一个环形队列。



每当存在空格时生产者就可以生产物品,并且按照顺序访问这些格子;

当存在物品时,消费者就可以进行消费,同样也是按照顺序访问。

当然,在环形队列这种情况下,生产者与消费者需要遵循一些原则:生产者优先,消费者一直处于落后状态,生产者也不能超出消费者一圈。

函数









其中sem_init()函数是初始化函数,参数value为信号量的值,而pshared一般设为0,表示信号量用于同一进程内线程间同步;而sem_destroy()是销毁函数;sem_wait()是P操作,将信号量的值-1;sem_post()是V操作,将信号量的值+1;sem_trywait()是尝试申请资源。

信号量实现的生产者与消费者模型



然后去运行代码得到结果为:



补充

如果我们遇到了多个生产者与多个消费者又应该如何去处理呢?

下面给上代码:



看完代码你懂了没,其实就是再加上一个互斥锁,保证一次只有一组,避免冲突的产生。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: