POSIX信号量与生产着消费者问题

生产者与消费者问题是一个非常经典的问题,以《UNIX网络编程 卷二》中的样例为例,现在有一个或者多个生产者和一个消费者,生产者负责将一个数组内的所有元素赋值,数组中每个元素的值应该与其下表的值相等,而消费者负责校验生产者生产出来的产品。这个问题看起来也许不是很复杂,不过仅仅通过互斥锁与条件变量来实现的话仍然比较麻烦。

这里一个无法避免的问题就是同步不同的生产者,使他们不重复生产或者错误生产,我们可以生产者线程(注意,本文的内容是在多线程环境下实现的)加上一个互斥锁,让这些线程能够协同生产,当生产量达到预定的标准时生产者线程应该退出。另一个问题是消费者怎么办,让消费者在所有生产者生产完毕之后再开始消费的话可以避免了生产者与消费者之间的协同问题,因为生产者此时早就已经结束了,因此不会再消费过程中出现过度消费的现象,此时我们只要互斥锁来同步生产者们的工作。但是这样做效率并不高,因为生产与消费的过程是串行的而不是并行的。我们也可以在仅仅使用互斥锁的条件下让生产者与消费者在几近相同的时间启动,这需要使用轮询技术。我们仍然使用互斥锁来同步生产者们,在消费者中需要不停的询问生产的量是否大于消费者当前消费的量,如果生产量大于当前消费量时消费者开始继续消费,否则消费者将开始不断地轮询(如果消费完了所有期望的生产值,消费者将退出)。这样做确实实现了生产者与消费者的并发执行,不过主要问题出现在消费者身上,消费者采用的轮询技术大大的浪费了CPU时间。稍有些经验的程序员都会注意到,如果一个程序陷入长期的循环,那么CPU使用率会变得比较高,故我们在实际编程中应该避免轮询技术。我们可以在生产者和消费者身上做出少量的改动,使用条件变量来通知消费者此时有过尚未被消费的生产量,当然我们还需要借助另一把互斥锁,因为条件变量是依赖于互斥锁的。同时我们还需要统计出当前有多少生产量没有被消费,如果这个值为0,那么消费者需要等待生产者的通知,直到消费者生产出一些新的东西来才能继续消费。使用条件变量的过程中需要注意锁的控制,在读写统计有多少生产量没有被消费的变量时需要获取一把锁,这把锁不同于同步生产者生产情况的锁,而是与消费等待条件变量而是用的互斥锁是同一把锁!(代码详见《UNIX网络编程 卷二》)

之后我们可以使用信号量再一次优化一下,使用信号量不仅可以简化代码的书写,而且还可以避免一些相对复杂的加锁操作。一个比较好的方法就是使用两个信号量,信号量A用于同步生产之间的生产,信号量B用于同步消费者与生产者之间的关系。这里使用信号量是利用了信号量的性质,尤其是信号量的等待和挂出操作都是原子性的,这可以让我们避免了复杂的加锁过程。但是使用信号量需要注意的是要像使用锁那样注意对应等待与挂出操作,而且要在程序结束的时候及时关闭并删除信号量。当你使用一个信号量(有名POSIX信号量)时要知道它是随内核持续的,并不因为没有进程(线程)使用它而消失,内核在维护者一个信号量。

你可能感兴趣的:(POSIX信号量与生产着消费者问题)