生产者消费者问题

来源:互联网 发布:地牢猎手5网络出错 编辑:程序博客网 时间:2024/05/08 17:11
1. 问题定义
某些进程生产数据,另一些进程消费数据,他们之间通过一个大小为N的先入先出队列来进行数据的传递。
当队列是满的时候,生产者阻塞,当队列是空的时候,消费者阻塞。
这种场景还是挺常见的,比如媒体流的处理。
2. PV原语
PV原语,这个概念的提出者是河南(荷兰)科学家Dijkstra,这位爷也提出了图的Dijkstra最短路径算法。原语的意思是atomic操作,就是无法分割,不可打断的操作。PV原语是用来操作信号量的,笼统的说,P操作减少信号量,V操作增加信号量,精确的说,请看伪代码:
procedure P(var s:samephore);{     s.value=s.value-1;     if (s.value<0) asleep(s.queue);}procedure V(var s:samephore);{     s.value=s.value+1;     if (s.value<=0) wakeup(s.queue);}


这里的s是信号量(samephore),s.queue是指s的阻塞进程队列,这也是一个先入先出队列。asleep(s.queue)意味着把本进程放入队列,wakeup(s.queue)意味着把位于队列头的一个进程唤醒,所谓唤醒,也就是放入操作系统的就绪队列。
3. 解决问题
生产者消费者问题的完美解决需要三个信号量:fullCount,emptyCount和useQueue。顾名思义,fullCount意味着队列中元素的个数,emptyCount意味着队列中空位的个数,useQueue意味着当前是不是有进程在访问队列。在跑起来的时候,emptyCount是会小于队列中空位的个数的,比如当很多生产者同时等待useQueue时;同理,fullCount也是会小于队列中元素的个数的,比如很多消费者同时等待useQueue时------好像东西还在你的仓库里,而你已经把它预售出去了一样。所以fullCount+emptyCount <= N,但是,当生产者进程和消费者进程都只有一个的情况下,fullCount+emptyCount == N。
注意,这里的useQueue是个取值为0或1的信号量,也叫互斥型信号量,即mutex。
初始化fullCount.value为0,emptyCount.value 为N,useQueue.value为1,那么:
produce:    P(emptyCount)    P(useQueue)    putItemIntoQueue(item)    V(useQueue)    V(fullCount)consume:    P(fullCount)    P(useQueue)    item ← getItemFromQueue()    V(useQueue)    V(emptyCount)


4. 例子
Android中AudioTrack和AudioFlinger的数据交换是个简化版的生产者消费者问题,说它简化,是因为首先生产者,消费者都只有一个,其次共享数据的只有一个Buffer,也就是说队列的N==1,每次生产者都努力把Buffer填满,消费者都努力把Buffer消费干净。
所以,仅仅需要一个useQueue就能满足需求了!
这个信号量在audio_track_cblk_t 中定义,这是一个环形Buffer,循环的写和读,顺便说一句,环形buffer关键就是记录要读和写的位置,根据这些,也可以得到buffer是空还是满。
在audio_track_cblk_t 中,有一个volatile int32_tmFutex, 这个是生产者消费者PV操作的关键,代码中注释是:event flag: down (P) by client, up (V) by server or binderDied() or interrupt()。 相应的PV操作是__futex_syscall4,类似于Wait,还有__futex_syscall3,类似于Signal。这俩实现了对进程的阻塞和唤醒。
在这个简化的模型中,AudioTrack是生产者,它每次都写满buffer,然后sleep,并wakeup消费者;AudioFlinger是消费者,它每次都读空buffer,然后sleep,并wakeup生产者。
0 0
原创粉丝点击