先來描述一下待解決的問題:
有一個倉庫,它最多有七個槽位,最開始每個槽位都是空的。當有空槽位的時候,允許生產者往裡面放東西。當槽位上有東西時,允許消費者從裡面拿東西;滿了則不允許再放,空了則不允許再拿。因為倉庫設計問題,同一時間內,只允許一個人進去放東西或者拿東西。需要盡最大的效率安排生產者和消費者的工作。可以看到,這裡生產和消費的動作不需要做嚴格的同步,只要規則允許,可以連續產生三次,也可以連續消費三次。而沒有說一定要生產一次,消費一次,再生產一次,再消費一次的順序來。
// productor_consumer.cpp
// Windows 平台對生產者-消費者問題的解決方案
#include <windows.h>
#include <tchar.h>
#include <list>
// 定義倉庫的最大槽位
#define WH_SLOT 7
// 定義等待號誌的逾時時間
#define TIMEOUT 3000
// 定義倉庫類
class Warehource
{
public:
Warehource()
{
// 建立一個訪問倉庫的互斥量,生產者和消費者不能同時訪問倉庫
m_hAccess = CreateEvent(NULL, FALSE, TRUE, NULL);
}
~Warehource()
{
// 關閉訪問互斥量
CloseHandle(m_hAccess);
}
// 放入到倉庫
BOOL put(char ch)
{
// 嘗試擷取互斥量
if (WAIT_OBJECT_0 == WaitForSingleObject(m_hAccess, 100))
{
m_slot.push_back(ch);
printf("Product an item, now warehourse size is %d\n", m_slot.size());
SetEvent(m_hAccess); //let's next access available
return TRUE;
}
return FALSE;
}
// 從倉庫取出
BOOL get(char* ch)
{
// 嘗試擷取互斥量
if (WAIT_OBJECT_0 == WaitForSingleObject(m_hAccess, 100))
{
*ch = m_slot.front();
m_slot.pop_front();
printf("Consume an item, now warehourse size is %d\n", m_slot.size());
SetEvent(m_hAccess); //let's next access available
return TRUE;
}
return FALSE;
}
private:
std::list<char> m_slot;
HANDLE m_hAccess; //對倉庫訪問要互斥
};
DWORD WINAPI ProductorRun(LPVOID ud);
DWORD WINAPI ConsumerRun(LPVOID ud);
HANDLE g_hProdExit; //生產者退出的訊號
HANDLE g_hFreeSemaphore; //倉庫空閑槽的號誌,最大值為WH_SLOT
HANDLE g_hConsExit; //消費者退出的訊號
HANDLE g_hDataSemaphore; //倉庫可用槽的號誌,最大值為WH_SLOT
int _tmain(int argc, _TCHAR* argv[])
{
Warehource wh;
g_hProdExit = CreateEvent(NULL, FALSE, FALSE, NULL);
g_hConsExit = CreateEvent(NULL, FALSE, FALSE, NULL);
//倉庫空閑槽的號誌,最大值為WH_SLOT,初始值為WH_SLOT,因為倉庫
//一開始都是空的
g_hFreeSemaphore = CreateSemaphore(NULL, WH_SLOT, WH_SLOT, NULL);
//倉庫可用槽的號誌,最大值為WH_SLOT,初始值為0,因為倉庫
//一開始都是空的
g_hDataSemaphore = CreateSemaphore(NULL, 0, WH_SLOT, NULL);
// 建立生產者和消費者
HANDLE hProductor = CreateThread(NULL, NULL, ProductorRun, &wh, 0, 0);
HANDLE hConsumer = CreateThread(NULL, NULL, ConsumerRun, &wh, 0, 0);
// let main thread sleep 30 seconds
Sleep(3000);
// 結束生產者
SetEvent(g_hProdExit);
WaitForSingleObject(hProductor, INFINITE);
CloseHandle(g_hProdExit);
CloseHandle(hProductor);
// 結束消費者
SetEvent(g_hConsExit);
WaitForSingleObject(hConsumer, INFINITE);
CloseHandle(g_hConsExit);
CloseHandle(hConsumer);
// 關閉號誌
CloseHandle(g_hFreeSemaphore);
CloseHandle(g_hDataSemaphore);
return 0;
}
DWORD WINAPI ProductorRun(LPVOID ud)
{
BOOL fDone = FALSE;
HANDLE h2[] = { g_hProdExit, g_hFreeSemaphore };
DWORD dwWait = 0;
Warehource* wh = (Warehource*)ud;
while (!fDone)
{
// 等待退出訊號或空閑訊號
dwWait = WaitForMultipleObjects(2, h2, FALSE, TIMEOUT);
if (dwWait == WAIT_OBJECT_0)
{
//退出訊號
fDone = TRUE;
}
else if (dwWait == WAIT_OBJECT_0+1)
{
// 空閑訊號,表示倉庫中有空閑槽,嘗試放入到倉庫
if (wh->put('a'))
{
// 已經成功放入一個到倉庫,則置上一個可用槽的號誌
ReleaseSemaphore(g_hDataSemaphore, 1, NULL);
}
else //訪問倉庫逾時
{
// 將熄滅的空閑槽號誌置回去
ReleaseSemaphore(g_hFreeSemaphore, 1, NULL);
}
}
}
return 0;
}
DWORD WINAPI ConsumerRun(LPVOID ud)
{
BOOL fDone = FALSE;
HANDLE h2[] = { g_hConsExit, g_hDataSemaphore };
DWORD dwWait = 0;
Warehource* wh = (Warehource*)ud;
while (!fDone)
{
// 等待退出訊號或可用訊號
dwWait = WaitForMultipleObjects(2, h2, FALSE, TIMEOUT);
if (dwWait == WAIT_OBJECT_0)
{
//退出訊號
fDone = TRUE;
}
else if (dwWait == WAIT_OBJECT_0+1)
{
// 可用訊號,表示倉庫中某個槽已有資料,嘗試從中取用
char ch;
if (wh->get(&ch))
{
// 已經成功從倉庫取出一個,則置上一個空閑槽的號誌
ReleaseSemaphore(g_hFreeSemaphore, 1, NULL);
}
else //訪問倉庫逾時
{
// 將熄滅的可用槽號誌置回去
ReleaseSemaphore(g_hDataSemaphore, 1, NULL);
}
}
}
return 0;
}
注意semaphore是可以讓多個線程同時訪問共用資源的唯一訊號類型。