生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
首先我们实现这个问题的简化版本。
先假设生产者和消费者都只有一个,且缓冲区也只有一个。第一.从缓冲区取出产品和向缓冲区投放产品必须是互斥进行的。第二.生产者要等待缓冲区为空,这样才可以投放产品,消费者要等待缓冲区不为空,这样才可以取出产品进行消费。并且由于有二个等待过程,可见这两个过程不仅是互斥而且还是同步的。我们可以使用两个信号量来控制这两个同步过程。
代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using ThreadUtilities;
namespace BoundedBufferProblem
{
class Program
{
static void Main(string[] args)
{
SimpleProducerConsumer simpleboundedbuffer = new SimpleProducerConsumer();
simpleboundedbuffer.Start();
simpleboundedbuffer.Stop(2000);
}
}
#region SimpleProducerConsumer
/// <summary>
/// the class mock the Bounded Buffer proble with one producer and one custom and one buffer pool
/// </summary>
class SimpleProducerConsumer
{
Semaphore mFullBuffer;
Semaphore mEmptyBuffer;
CustomResetEvent mWaitHandle = new CustomResetEvent(2);
bool mbStop = false;// to give simbol whether the process sub should stop
int mCurrentProductID = 0;// use as the productor id
public void Start()
{
mFullBuffer = new Semaphore(0, 1);
mEmptyBuffer = new Semaphore(1, 1);
Thread produceth = new Thread(Prouduce);
produceth.Start();
Thread consumeth = new Thread(Consume);
consumeth.Start();
}
public void Stop(int durationforrunning)
{
Thread.Sleep(durationforrunning);// make durationforrunning for producer thread and consumer thread
mbStop = true;
mWaitHandle.Wait();
Console.WriteLine("Producer thread and Consumer thread have working for {0} seconds!", (float)durationforrunning / 1000);
mFullBuffer.Dispose();
mEmptyBuffer.Dispose();
}
void Prouduce()
{
while (mbStop == false)
{
mEmptyBuffer.WaitOne();
Thread.Sleep(100);
mCurrentProductID += 1;
Console.WriteLine("The Product " + mCurrentProductID + " is ready!");
mFullBuffer.Release();
}
//end the producing
mWaitHandle.SignalFinishOne();
}
void Consume()
{
while (mbStop == false)
{
mFullBuffer.WaitOne();
Thread.Sleep(100);
Console.WriteLine(" The Product " + mCurrentProductID + " is taken away!");
mEmptyBuffer.Release();
}
mWaitHandle.SignalFinishOne();
}
}
#endregion SimpleProducerConsumer
}
运行结果:
然后再对这个简单生产者消费者问题加大难度,将生产者,消费者,缓冲区的个数都改为大于一个。因为我们上面已经强调过,生产者和消费者对同一个缓冲区的访问是互斥的,因此,生产者和消费者都由一个变为多个影响不大,唯一要考虑的是缓冲区变多了,这就要考虑多个生者同时写入几个空的缓冲区或者多个消费者争取几个非空的缓冲区,因此我们可以考虑用一个信号量来记录记录空缓冲区的个数,用另一个信号量记录非空缓冲区的个数。同时,因为有多个生产者,因为多个生产者可能同时争夺一个空缓冲区,因此用一个锁对象来保护生产过程;同理多个消费者可能同时争夺一非空缓冲区,也用一个锁对象来保护该过程。
代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using ThreadUtilities;
namespace BoundedBufferProblem
{
class Program
{
static void Main(string[] args)
{
ExtendedProducerConsumer boundedbufferEX = new ExtendedProducerConsumer(4, 3, 3);
boundedbufferEX.Start();
boundedbufferEX.Stop(800);
}
}
public class ExtendedProducerConsumer
{
int mProducerCount = 1;
int mBufferCount = 1;
int mConsumerCount = 1;
Semaphore mFullBuffers;
Semaphore mEmptyBuffers;
static Object mCSOfProducer = new Object();
static Object mCSOfConsumer = new Object();
Queue<int> mProducts;
int mCurProductID = 0;
int mCurFullBufferID = 0;
int mCurEmptyBufferID = 0;
bool mbStop = false;// to give simbol whether the process sub should stop
CustomResetEvent mWaitHandle;
public ExtendedProducerConsumer(int producercount, int buffercount, int consumercount)
{
mProducerCount = producercount;
mBufferCount = buffercount;
mConsumerCount = consumercount;
}
public void Start()
{
mFullBuffers = new Semaphore(0, mBufferCount);
mEmptyBuffers = new Semaphore(mBufferCount, mBufferCount);
mProducts = new Queue<int>();
mWaitHandle = new CustomResetEvent(mProducerCount + mConsumerCount);
for (int i = 0; i < mProducerCount; ++i)
{
Thread produceth = new Thread(Prouduce);
produceth.Start(produceth.ManagedThreadId);
}
for (int j = 0; j < mConsumerCount; ++j)
{
Thread consumeth = new Thread(Consuming);
consumeth.Start(consumeth.ManagedThreadId);
}
}
/// <summary>
///
/// </summary>
/// <param name="durationforrunning">longer time to insure all the thread have chance to run</param>
public void Stop(int durationforrunning)
{
Thread.Sleep(durationforrunning);// make durationforrunning for producer thread and consumer thread
mbStop = true;
mWaitHandle.Wait();
Console.WriteLine("Producer thread and Consumer thread have working for {0} seconds!",(float)durationforrunning/1000);
mFullBuffers.Dispose();
mEmptyBuffers.Dispose();
}
void Prouduce(object ProducerID)
{
while (mbStop == false)
{
mEmptyBuffers.WaitOne();
Monitor.Enter(mCSOfProducer);
mProducts.Enqueue(mCurProductID);
Console.WriteLine("The Product " + mCurProductID + " has been put into Buffer " + mCurFullBufferID + " By Producer " + ProducerID);
++mCurProductID;
mCurFullBufferID = (mCurFullBufferID + 1) % mBufferCount;
Thread.Sleep(100);
Monitor.Exit(mCSOfProducer);
mFullBuffers.Release();
}
mWaitHandle.SignalFinishOne();
}
void Consuming(object ConsumerID)
{
while (mbStop == false)
{
mFullBuffers.WaitOne();
Monitor.Enter(mCSOfConsumer);
int productid = mProducts.Dequeue();
Console.WriteLine(" The Product " + productid + " in Buffer " + mCurEmptyBufferID + " is taken away by Consumer " + ConsumerID);
mCurEmptyBufferID = (mCurEmptyBufferID + 1) % mBufferCount;
Thread.Sleep(100);
Monitor.Exit(mCSOfConsumer);
mEmptyBuffers.Release();
}
mWaitHandle.SignalFinishOne();
}
}
}
运行效果:
存在的问题:
因为生产过程和消费过程都有并发,因此实际上可能会有些线程处于“饥饿状态”;
在这个实现中,尽管有多个生产者并发或者多个消费者并发,但是实际上,因为buffer区只允许一个线程来写或者读,用了锁来保护这个过程,这样其实多线程只是“轮流”执行,并没有真正的并发。