一、實驗目的
(1) 回顧操作系統進程、線程的有關概念,加深對 Windows 線程的理解。
(2) 瞭解互斥體對象,利用互斥與同步操作編寫生產者-消費者問題的併發程序,加深對 P (即semWait)、 V(即 semSignal)原語以及利用 P、 V 原語進行進程間同步與互斥操作的理解。
二、總體設計
2.1 設計步驟
(1)生產者消費者問題
步驟 1:創建一個“Win32 Consol Application”工程,然後拷貝清單 3-1 中的程序,編譯成可執行文件。
步驟 2:在“命令提示符”窗口運行步驟 1 中生成的可執行文件,列出運行結果。
步驟 3:仔細閱讀源程序,找出創建線程的 WINDOWS API 函數,回答下列問題:線程的第一個執行函數是什麼(從哪裏開始執行)?它位於創建線程的 API 函數的第幾個參數中?
步驟 4:修改清單 3-1 中的程序,調整生產者線程和消費者線程的個數,使得消費者數目大與生產者,看看結果有何不同。察看運行結果,從中你可以得出什麼結論?
步驟 5:修改清單 3-1 中的程序,按程序註釋中的說明修改信號量 EmptySemaphore 的初始化方法,看看結果有何不同。
步驟 6:根據步驟 4 的結果,並查看 MSDN,回答下列問題:
1)CreateMutex 中有幾個參數,各代表什麼含義。
2)CreateSemaphore 中有幾個參數,各代表什麼含義,信號量的初值在第幾個參數中。
3)程序中 P、V 原語所對應的實際 Windows API 函數是什麼,寫出這幾條語句。
4)CreateMutex 能用 CreateSemaphore 替代嗎?嘗試修改程序 3-1,將信號量 Mutex 完全用CreateSemaphore 及相關函數實現。寫出要修改的語句。
三、詳細設計
#include <windows.h>
#include <iostream>
const unsigned short SIZE_OF_BUFFER = 2; //緩衝區長度
unsigned short ProductID = 0; //產品號
unsigned short ConsumeID = 0; //將被消耗的產品號
unsigned short in = 0; //產品進緩衝區時的緩衝區下標
unsigned short out = 0; //產品出緩衝區時的緩衝區下標
int buffer[SIZE_OF_BUFFER]; //緩衝區是個循環隊列
bool p_ccontinue = true; //控制程序結束
HANDLE Mutex; //用於線程間的互斥
HANDLE FullSemaphore; //當緩衝區滿時迫使生產者等待
HANDLE EmptySemaphore; //當緩衝區空時迫使消費者等待
DWORD WINAPI Producer(LPVOID); //生產者線程
DWORD WINAPI Consumer(LPVOID); //消費者線程
int main()
{
//創建各個互斥信號
//注意,互斥信號量和同步信號量的定義方法不同,互斥信號量調用的是 CreateMutex 函數,同步信號量調用的是 CreateSemaphore 函數,函數的返回值都是句柄。
Mutex = CreateMutex(NULL,FALSE,NULL);// 指向安全屬性的指針 // 初始化互斥對象的所有者 // 指向互斥對象名的指針
//EmptySemaphore = CreateSemaphore(NULL,SIZE_OF_BUFFER,SIZE_OF_BUFFER,NULL); //該參數定義了信號量的安全特性,設置信號量的初始計數。設置信號量的最大計數 指定信號量對象的名稱。
//將上句做如下修改,看看結果會怎樣
EmptySemaphore = CreateSemaphore(NULL,1,SIZE_OF_BUFFER-1,NULL);
FullSemaphore = CreateSemaphore(NULL,0,SIZE_OF_BUFFER,NULL);
//調整下面的數值,可以發現,當生產者個數多於消費者個數時,
//生產速度快,生產者經常等待消費者;反之,消費者經常等待
const unsigned short PRODUCERS_COUNT = 2; //生產者的個數
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 (int i=0;i<PRODUCERS_COUNT;++i){
hThreads[i]=CreateThread(NULL,0,Producer,NULL,0,&producerID[i]);//線程安全屬性,堆棧大小,線程函數,線程參數,線程創建屬性,線程ID
if (hThreads[i]==NULL) return -1;
}
//創建消費者線程
for (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(p_ccontinue){
if(getchar()){ //按回車後終止程序運行
p_ccontinue = false;
}
}
return 0;
}
//生產一個產品。簡單模擬了一下,僅輸出新產品的 ID 號
void Produce()
{
std::cout << std::endl<< "Producing " << ++ProductID << " ... ";
std::cout << "Succeed" << std::endl;
}
//把新生產的產品放入緩衝區
void Append()
{
std::cerr << "Appending a product ... ";
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 <<": " << buffer[i];
if (i==in) std::cout << " <-- 生產";
if (i==out) std::cout << " <-- 消費";
std::cout << std::endl;
}
}
//從緩衝區中取出一個產品
void Take()
{
std::cerr << "Taking a product ... ";
ConsumeID = buffer[out];
buffer[out] = 0;
out = (out+1)%SIZE_OF_BUFFER;
std::cerr << "Succeed" << std::endl;
//輸出緩衝區當前的狀態
for (int i=0;i<SIZE_OF_BUFFER;++i){
std::cout << i <<": " << buffer[i];
if (i==in) std::cout << " <-- 生產";
if (i==out) std::cout << " <-- 消費";
std::cout << std::endl;
}
}
//消耗一個產品
void Consume()
{
std::cout << "Consuming " << ConsumeID << " ... ";
std::cout << "Succeed" << std::endl;
}
//生產者
DWORD WINAPI Producer(LPVOID lpPara)
{
while(p_ccontinue){
WaitForSingleObject(EmptySemaphore,INFINITE); //p(empty);
WaitForSingleObject(Mutex,INFINITE); //p(mutex);
Produce();//生產一個產品。簡單模擬了一下,僅輸出新產品的 ID 號
Append();//把新生產的產品放入緩衝區
Sleep(1500);
ReleaseMutex(Mutex); //V(mutex);
ReleaseSemaphore(FullSemaphore,1,NULL); //V(full);
}
return 0;
}
//消費者
DWORD WINAPI Consumer(LPVOID lpPara)
{
while(p_ccontinue){
WaitForSingleObject(FullSemaphore,INFINITE); //P(full);
WaitForSingleObject(Mutex,INFINITE); //P(mutex);
Take();//從緩衝區中取出一個產品
Consume();//消耗一個產品
Sleep(1500);
ReleaseMutex(Mutex); //V(mutex);
ReleaseSemaphore(EmptySemaphore,1,NULL); //V(empty);
}
return 0;
}
四、實驗結果與分析
步驟二運行結果:
步驟三:
線程的第一個執行函數是Producer();。它位於創建線程的 API 函數的第三個參數中。
步驟四:
調整生產者和消費者的數值,可以發現,當生產者個數多於消費者個數時,生產速度快,生產者經常等待消費者;反之,消費者經常等待。
步驟五:
按程序註釋中的說明修改信號量 EmptySemaphore 的初始化方法,第一個和第四個參數一般都是NULL,第二個參數決定了初始值大小,第三個參數決定了最大值爲多少。
步驟六:
CreateMutex函數參數含義分別是:指向安全屬性的指針、初始化互斥對象的所有者、指向互斥對象名的指針。
CreateSemaphore函數參數含義分別是:指向安全屬性的指針、設置信號量的初始計數、設置信號量的最大計數、指定信號量對象的名稱。信號量的初值爲第2個參數中。
WaitForSingleObject(Mutex,INFINITE);
ReleaseMutex(Mutex);
ReleaseSemaphore(FullSemaphore,1,NULL);
ReleaseSemaphore(EmptySemaphore,1,NULL);
可以使用CreateSemaphore 來替代CreateMutex。因爲達到的效果一致。
Mutex = CreateMutex(NULL,FALSE,NULL);
改爲Mutex = CreateSemaphore(NULL,1,1,NULL);
生產者,消費者內: ReleaseMutex(Mutex);
改爲 ReleaseSemaphore(Mutex,1,NULL);
五、小結與心得體會
通過本實驗加深了我對 Windows 線程的理解。瞭解了互斥體對象,利用互斥與同步操作編寫生產者-消費者問題的併發程序,加深對 P (即semWait)、 V(即 semSignal)原語以及利用 P、 V 原語進行進程間同步與互斥操作的理解。