多线程同步中的生产者消费者问题
生产者消费者问题是多线程同步中的一个经典问题,类似的还有读者写者问题和哲学家就餐问题。本文讨论生产者消费者问题,并分别在
windows
和
linux
平台上进行了程序实现。
生产者消费者问题的基本概念是:一个或多个生产者向一个缓冲区添加数据
(
或消息
)
,一个或多个消费者使用这些数据完成特定的功能。每个生产者是一个独立线程,每个消费者也是一个线程。例如,
2
个线程从网络
socket
接收数据,放到一个缓冲区,另外
3
个线程负责处理这些数据。生产者消费者问题主要应该解决线程之间的互斥和同步问题。
生产者消费者问题与读者写者问题有些不同,因为读者写者问题中,读者不修改任何共享变量,而生产者消费者问题中消费者也需要修改某些共享变量。另外,生产者消费者问题中还要考虑缓冲区的满和空的问题。根据生产者和消费者是否修改同一个共享变量,可以分成两种情况:
(1)
生产者和消费者需要修改同一个共享变量:
如果共享缓冲区是一个类似于栈的东西,如下图所示:
其中
P
和
C
分别表示生产者和消费者当前位置指示器,二者共享栈顶指针。我们用一个互斥量保护共享的栈顶指针变量,该互斥量不仅用于防止生产者和消费者对共享变量的修改,而且用于生产者之间以及消费者之间的互斥访问。我们用两个信号量分别指示栈的空和满,以协调生产者和消费者之间的操作。
(2)
生产者和消费者不需要修改共享变量:
如果共享缓冲区是一个类似于循环队列的东西,如下图所示:
生产者和消费者当前位置指示器
P
和
C
不共享变量,各自维护自己的指针。不需要一个互斥量来防止生产者和消费者对共享变量的修改,但是需要两个互斥量分别用于生产者之间和消费者之间的互斥。同样需要两个信号量分别指示队列的空和满。
我们只对第二种情况进行程序实现,因为第一种情况相当于第二种情况的一个特殊情况。
(1) ProducerConsumerLock.h
#ifndef ProducerConsumerLock_H
#define ProducerConsumerLock_H
class ProducerConsumerLock
{
protected:
HANDLE produceMutex;
HANDLE consumeMutex;
HANDLE wakenProducerSemaph;
HANDLE wakenConsumerSemaph;
unsigned int bufferLen;
public:
ProducerConsumerLock(unsigned int bufLen=100)
{
bufferLen = bufLen;
// create 2 Mutex and 2 Semaphore
produceMutex = CreateMutex(NULL, false, NULL);
consumeMutex = CreateMutex(NULL, false, NULL);
wakenProducerSemaph = CreateSemaphore(NULL, bufferLen, bufferLen, NULL);
wakenConsumerSemaph = CreateSemaphore(NULL, 0, bufferLen, NULL);
if (produceMutex == NULL || consumeMutex == NULL ||
wakenProducerSemaph == NULL || wakenConsumerSemaph == NULL)
{
MessageBox(NULL, "create LOCK failed!" , NULL, 0);
}
}
~ProducerConsumerLock()
{
CloseHandle(produceMutex);
CloseHandle(consumeMutex);
CloseHandle(wakenProducerSemaph);
CloseHandle(wakenConsumerSemaph);
}
inline void ProducerLock()
{
// if the buffer is full, then wait
WaitForSingleObject(wakenProducerSemaph, INFINITE);
// if other producer is producing, then wait
WaitForSingleObject(produceMutex, INFINITE);
}
inline void ProducerUnLock()
{
// notify other producer
ReleaseMutex(produceMutex);
// waken consumers
ReleaseSemaphore(wakenConsumerSemaph, 1, NULL);
}
inline void ConsumerLock()
{
// if the buffer is empty, then wait
WaitForSingleObject(wakenConsumerSemaph, INFINITE);
// if other consumer is consuming, then wait
WaitForSingleObject(consumeMutex, INFINITE);
}
inline void ConsumerUnLock()
{
// notify other consumer
ReleaseMutex(consumeMutex);
// waken producers
ReleaseSemaphore(wakenProducerSemaph, 1, NULL);
}
};
#endif
(2) ProducerConsumer.cpp
// ProducerConsumer.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
#include
#include
#include
#include
#include
#include
#include "ProducerConsumerLock.h"
char sharedQueue[128]={0};
const unsigned int queueLen = 10;
unsigned int producerPointer=0;
unsigned int consumerPointer=0;
char logFile[128]="d://log.txt";
ProducerConsumerLock* pcLock=new ProducerConsumerLock(10);
void producerProc(void* param);
void consumerProc(void* param);
void WriteLogStr(char* s);
using namespace std;
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
int producerId;
int consumerId;
FILE* f;
// clear the log
f = fopen(logFile, "w");
fclose(f);
srand(time(0));
producerId = 1;
_beginthread(producerProc, 0, (void*)&producerId);
Sleep(10);
consumerId = 1;
_beginthread(consumerProc, 0, (void*)&consumerId);
Sleep(10);
producerId = 2;
_beginthread(producerProc, 0, (void*)&producerId);
Sleep(10);
consumerId = 2;
_beginthread(consumerProc, 0, (void*)&consumerId);
Sleep(10);
producerId = 3;
_beginthread(producerProc, 0, (void*)&producerId);
// running for 5s
Sleep(5000);
return 0;
}
void producerProc(void* param)
{
int myid;
char idStr[128];
//char tmpStr[128];
char str[128];
myid = *((int*)(param));
itoa(myid, idStr, 10);
strcpy(str, "producer ");
strncat(str, idStr, 128);
strcat(str, " begin......");
//cout << "reader " << myid << " begin......" << endl;
WriteLogStr(str);
while (true)
{
// first sleep a random time : between 1 - 5 s
int sleepTime;
sleepTime = 1 + (int)(5.0*rand()/(RAND_MAX+1.0));
Sleep(sleepTime*10);
// get a random char
int randChar;
randChar = myid + (int)(5.0*rand()/(RAND_MAX+1.0));
randChar += 40;
// prepare str
strcpy(str, "producer ");
strncat(str, idStr, 128);
strcat(str, " has produced a new char : ");
int len = strlen(str);
str[len] = randChar;
str[len+1] = 0;
// then access the shared var
pcLock->ProducerLock();
sharedQueue[producerPointer] = randChar;
producerPointer = (producerPointer + 1) % queueLen;
if (producerPointer == consumerPointer)
{
WriteLogStr("The buffer queue is full !!!");
}
//itoa(producerPointer, tmpStr, 10);
//strncat(str, tmpStr, 128);
//cout << "reader " << myid << " is reading the shared string : " << sharedStr << endl;
WriteLogStr(str);
pcLock->ProducerUnLock();
}
}
void consumerProc(void* param)
{
int myid;
char idStr[128];
//char tmpStr[128];
char str[128];
myid = *((int*)(param));
itoa(myid, idStr, 10);
strcpy(str, "consumer ");
strncat(str, idStr, 128);
strcat(str, " begin......");
//cout << "reader " << myid << " begin......" << endl;
WriteLogStr(str);
while (true)
{
// first sleep a random time : between 1 - 5 s
int sleepTime;
sleepTime = 1 + (int)(5.0*rand()/(RAND_MAX+1.0));
Sleep(sleepTime*10);
// prepare str to print
strcpy(str, "consumer ");
strncat(str, idStr, 128);
strcat(str, " has consumed a char : ");
// then access the shared queue
pcLock->ConsumerLock();
int len = strlen(str);
str[len] = sharedQueue[consumerPointer];
str[len+1] = 0;
sharedQueue[consumerPointer] = 0;
consumerPointer = (consumerPointer + 1) % queueLen;
//itoa(consumerPointer, tmpStr, 10);
//strncat(str, tmpStr, 128);
//cout << "reader " << myid << " is reading the shared string : " << sharedStr << endl;
WriteLogStr(str);
pcLock->ConsumerUnLock();
}
}
void WriteLogStr(char* s)
{
FILE* f;
f = fopen(logFile, "a");
if (f != NULL)
{
fwrite(s, strlen(s), 1, f);
fwrite("/n", 1, 1, f);
}
fclose(f);
}