生產者-與消費者問題
1.實驗目的
本實驗的目的是通過編寫和除錯一個解決生產者-消費者問題的簡單模擬程式,進一步深入理解課堂教學中講授的程序同步問題,以及用於解決同步問題的訊號量機制的基本思想,即通過研究程序同步和訊號量機制實現生產者消費者問題的併發控制,以便階段性的鞏固學習成果。
2.實驗內容以及概述
(1)概述
說明:有界緩衝區內設有20個儲存單元,放入/取出的資料項設定為1~20這20個整型數。
編制程式模擬解決生產者-消費者同步問題。具體設計要求:
1)每個生產者和消費者對有界緩衝區進行操作後,即時顯示有界緩衝區的全部內容,當前指標位置和生產者/消費執行緒的識別符號。
2)生產者和消費者各有兩個以上
3)多個生產者或多個消費者之間須有共享對緩衝區進行操作的函式程式碼
(2)設計原理
通過一個有界緩衝區把生產者和消費者聯絡起來。假定生產者和消費者的優先順序是相同的,只要緩衝區未滿,生產者就可以生產產品並將產品送入緩衝區。類似地,只要緩衝區未空,消費者就可以從緩衝區取走產品。應該禁止生產者向未滿的緩衝區送入產品,同時也應該禁止消費者從空的緩衝區取走產品,在這一機制下由生產者執行緒和消費者執行緒之間的互斥關係來實現。與計算列印兩程序同步關係不同,生產者和消費者兩程序producer和consumer之間應滿足下列兩個同步條件:
a)只有在緩衝區中至少有一個緩衝區已存入訊息後,消費者才能從中提取資訊,否則 消費者必須等待
b)只有緩衝池中至少有一個緩衝區是空時,生產者才能把資訊放入緩衝區,否則生產者必須等待。
為了滿足第一個同步條件,設定一個同步訊號量full,它代表的是緩衝區滿,它的初始值為0,它的值為n時整個 緩衝池滿。這個資源時消費者程序consumer所有,consumer程序可以申請該資源,對它施加p操作,而consumer程序的合作程序producer對它施加v操作。同樣為了滿足第二個同步條件,設定另一個同步訊號量empty,它代表資源是緩衝空區,它的初始值為n,表示緩衝池中所有緩衝區空。訊號量full表示可用緩衝區數量,訊號量empty表示緩衝區數量,設定整形變數:存入指標in和取出指標out。
為了解決生產者和消費者問題,應該設定兩個資源訊號量,其中一個表示空緩衝區的數目,用g_hEmptySemaphore表示,其初始值為有界緩衝區的大小SIZE_OF_BUFFER;另一個表示緩衝區中產品的數目,用g_hFullSemaphore表示,其初始值為0。另外,由於有界緩衝區是一個臨界資源,必須互斥使用,所以還需要在設定一個互斥訊號量g_hMutex,其初始值為1.
p原語的主要動作是:
a)sem(訊號量)減1;
b)若sem減一後仍大於或等於零,則程序繼續進行;
c)若程序減一後小於零,則該程序被阻塞後與該訊號相對應的佇列中,然後轉程序排程。
v原語的主要操作是:
a)sem加一;
b)若相加結果大於零,程序繼續進行;
c)若相加結果小於或等於零,則從該訊號的等待佇列中喚醒一等待程序然後在返回原程序繼續執行或轉程序排程。
採用的同步方法:
a)利用函式CreateMutex(NULL,FALSE,NULL)建立互斥訊號量g_hMutex,表示緩衝區當前的狀態,若為true時,則表示緩衝區正被別的程序使用。三個引數表示的意義是:指向安全屬性的指標,初始化互斥物件的所有者,指向互斥物件名的指標。
b)利用函式CreateSemaphore(NULL,SIZE_OF_BUFFER,NULL)建立緩衝區空的訊號量g_hEmptySemaphore,值為true時表示緩衝區已滿。四個引數分別為:表示是否允許繼承.設定訊號機構的初始計數.設定訊號機的最大計數.指定訊號機物件的名稱(-1是因為計數從開始)。
c)利用函式CreateSemaphore(NULL,0,SIZE_OF_BUFFER,NULL)建立緩衝區滿的訊號量g_hFullSemaphore,該值為true時表示緩衝區為空。
#include <windows.h>#include <iostream> const unsigned short SIZE_OF_BUFFER = 20; //緩衝區長度
unsigned short ProductID = 0; //產品號
unsigned short ConsumeID = 0; //將被消耗的產品號
unsigned short in = 0; //產品進緩衝區時的緩衝區下標
unsigned short out = 0; //產品出緩衝區時的緩衝區下標 int g_buffer[SIZE_OF_BUFFER]; //緩衝區是個迴圈佇列
bool g_continue = true; //控制程式結束
HANDLE g_hMutex; //用於執行緒間的互斥
HANDLE g_hFullSemaphore; //當緩衝區空時迫使消費者等待
HANDLE g_hEmptySemaphore; //當緩衝區滿時迫使生產者等待 DWORD WINAPI Producer(LPVOID); //生產者執行緒
DWORD WINAPI Consumer(LPVOID); //消費者執行緒 int main()
{
g_hMutex = CreateMutex(NULL, FALSE, NULL);
//加註釋,說明上面函式的功能,各引數的含義
g_hEmptySemaphore = CreateSemaphore(NULL, SIZE_OF_BUFFER, SIZE_OF_BUFFER, NULL);
//加註釋,說明上面函式的功能,各引數的含義 g_hFullSemaphore = CreateSemaphore(NULL, 0, SIZE_OF_BUFFER, NULL);
const unsigned short PRODUCERS_COUNT = 3; //生產者的個數
const unsigned short CONSUMERS_COUNT = 1; //消費者的個數
const unsigned short THREADS_COUNT = PRODUCERS_COUNT + CONSUMERS_COUNT; //總的程序數 HANDLE hThreads[THREADS_COUNT]; //各執行緒的handle
DWORD producerID[PRODUCERS_COUNT]; //生產者執行緒的識別符號
DWORD consumerID[CONSUMERS_COUNT]; //消費者執行緒的識別符號
//加註釋,說明該For迴圈的作用
for (int i = 0; i<PRODUCERS_COUNT; ++i) {
hThreads[i] = CreateThread(NULL, 0, Producer, NULL, 0, &producerID[i]);
//加註釋,說明上面函式的功能,各引數的含義
if (hThreads[i] == NULL) return -1;
}
//加註釋,說明該For迴圈的作用
for (int i = 0; i<CONSUMERS_COUNT; ++i) {
hThreads[PRODUCERS_COUNT + i] = CreateThread(NULL, 0, Consumer, NULL, 0, &consumerID[i]);
if (hThreads[i] == NULL) return -1;
} while (g_continue) {
if (getchar()) { //按回車後終止程式執行
g_continue = false;
}
}
return 0;
} //加註釋,說明下面函式的功能
void Produce()
{
std::cerr << "Producing " << ++ProductID << " ... ";
std::cerr << "Succeed" << std::endl;
} //加註釋,說明下面函式的功能
void Append()
{
std::cerr << "Appending a product ... ";
g_buffer[in] = ProductID;
in = (in + 1) % SIZE_OF_BUFFER;
std::cerr << "Succeed" << std::endl; //輸出緩衝區當前的狀態
for (int i = 0; i<SIZE_OF_BUFFER; ++i) {
std::cout << i << ": " << g_buffer[i];
if (i == in) std::cout << " <-- 生產";
if (i == out) std::cout << " <-- 消費";
std::cout << std::endl;
}
} //加註釋,說明下面函式的功能
void Take()
{
std::cerr << "Taking a product ... ";
ConsumeID = g_buffer[out];
out = (out + 1) % SIZE_OF_BUFFER;
std::cerr << "Succeed" << std::endl; //輸出緩衝區當前的狀態
for (int i = 0; i<SIZE_OF_BUFFER; ++i) {
std::cout << i << ": " << g_buffer[i];
if (i == in) std::cout << " <-- 生產";
if (i == out) std::cout << " <-- 消費";
std::cout << std::endl;
}
}
//加註釋,說明下面函式的功能
void Consume()
{
std::cerr << "Consuming " << ConsumeID << " ... ";
std::cerr << "Succeed" << std::endl;
} //加註釋,說明下面函式的功能
DWORD WINAPI Producer(LPVOID lpPara)
{
while (g_continue) {
WaitForSingleObject(g_hEmptySemaphore, INFINITE);
//加註釋,說明上面函式的功能,各引數的含義
WaitForSingleObject(g_hMutex, INFINITE);
Produce();
Append();
Sleep(1500); ReleaseMutex(g_hMutex);
//加註釋,說明上面函式的功能,各引數的含義
ReleaseSemaphore(g_hFullSemaphore, 1, NULL);
//加註釋,說明上面函式的功能,各引數的含義 }
return 0;
} //加註釋,說明下面函式的功能
DWORD WINAPI Consumer(LPVOID lpPara)
{
while (g_continue) {
WaitForSingleObject(g_hFullSemaphore, INFINITE);
WaitForSingleObject(g_hMutex, INFINITE);
Take();
Consume();
Sleep(1500);
ReleaseMutex(g_hMutex);
ReleaseSemaphore(g_hEmptySemaphore, 1, NULL);
}
return 0;
}