互斥與同步

一、實驗目的

    (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 及相關函數實現。寫出要修改的語句。 

三、詳細設計

清單3-1:
#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個參數中。

    程序中 P、V 原語所對應的實際 Windows API 函數是WaitForSingleObject()、ReleaseMutex()和ReleaseSemaphore()。代碼中的語句: 
     WaitForSingleObject(EmptySemaphore,INFINITE);

     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 原語進行進程間同步與互斥操作的理解。 



發佈了68 篇原創文章 · 獲贊 186 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章