信號量(Semaphore)內(nèi)核對象對線程的同步方式與前面幾種方法不同,它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數(shù)目。在
用CreateSemaphore()創(chuàng)建信號量時即要同時指出允許的最大資源計數(shù)和當(dāng)前可
用資源計數(shù)。一般是將當(dāng)前可
用資源計數(shù)設(shè)置為最大資源計數(shù),每增加一個線程對共享資源的訪問,當(dāng)前可
用資源計數(shù)就會減1,只要當(dāng)前可
用資源計數(shù)是大于0的,就可以發(fā)出信號量信號。但是當(dāng)前可
用計數(shù)減小到0時則說明當(dāng)前占
用資源的線程數(shù)已經(jīng)達到了所允許的最大數(shù)目,不能在允許其他線程的進入,此時的信號量信號將無法發(fā)出。線程在處理完共享資源后,應(yīng)在離開的同時通過ReleaseSemaphore()函數(shù)將當(dāng)前可
用資源計數(shù)加1。在任何時候當(dāng)前可
用資源計數(shù)決不可能大于最大資源計數(shù)。

圖3 使
用信號量對象控制資源
下面結(jié)合圖例3來演示信號量對象對資源的控制。在圖3中,以箭頭和白色箭頭表示共享資源所允許的最大資源計數(shù)和當(dāng)前可
用資源計數(shù)。初始如圖(a)所示,最大資源計數(shù)和當(dāng)前可
用資源計數(shù)均為4,此后每增加一個對資源進行訪問的線程(
用黑色箭頭表示)當(dāng)前資源計數(shù)就會相應(yīng)減1,圖(b)即表示的在3個線程對共享資源進行訪問時的狀態(tài)。當(dāng)進入線程數(shù)達到4個時,將如圖(c)所示,此時已達到最大資源計數(shù),而當(dāng)前可
用資源計數(shù)也已減到0,其他線程無法對共享資源進行訪問。在當(dāng)前占有資源的線程處理完畢而退出后,將會釋放出空間,圖(d)已有兩個線程退出對資源的占有,當(dāng)前可
用計數(shù)為2,可以再允許2個線程進入到對資源的處理。可以看出,信號量是通過計數(shù)來對線程訪問資源進行控制的,而實際上信號量確實也被稱作Dijkstra計數(shù)器。
使
用信號量內(nèi)核對象進行線程同步主要會
用到
CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函數(shù)。其中,
CreateSemaphore()
用來創(chuàng)建一個信號量內(nèi)核對象,其函數(shù)原型為:
HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全屬性指針 LONG lInitialCount, // 初始計數(shù) LONG lMaximumCount, // 最大計數(shù) LPCTSTR lpName // 對象名指針 ); |
參數(shù)lMaximumCount是一個有符號32位值,定義了允許的最大資源計數(shù),最大取值不能超過4294967295。lpName參數(shù)可以為創(chuàng)建的信號量定義一個名字,由于其創(chuàng)建的是一個內(nèi)核對象,因此在其他進程中可以通過該名字而得到此信號量。OpenSemaphore()函數(shù)即可
用來根據(jù)信號量名打開在其他進程中創(chuàng)建的信號量,函數(shù)原型如下:
HANDLE OpenSemaphore( DWORD dwDesiredAccess, // 訪問標(biāo)志 BOOL bInheritHandle, // 繼承標(biāo)志 LPCTSTR lpName // 信號量名 ); |
在線程離開對共享資源的處理時,必須通過ReleaseSemaphore()來增加當(dāng)前可
用資源計數(shù)。否則將會出現(xiàn)當(dāng)前正在處理共享資源的實際線程數(shù)并沒有達到要限制的數(shù)值,而其他線程卻因為當(dāng)前可
用資源計數(shù)為0而仍無法進入的情況。ReleaseSemaphore()的函數(shù)原型為:
BOOL ReleaseSemaphore( HANDLE hSemaphore, // 信號量句柄 LONG lReleaseCount, // 計數(shù)遞增數(shù)量 LPLONG lpPreviousCount // 先前計數(shù) ); |
該函數(shù)將lReleaseCount中的值添加給信號量的當(dāng)前資源計數(shù),一般將lReleaseCount設(shè)置為1,如果需要也可以設(shè)置其他的值。WaitForSingleObject()和WaitForMultipleObjects()主要
用在試圖進入共享資源的線程函數(shù)入口處,主要
用來判斷信號量的當(dāng)前可
用資源計數(shù)是否允許本線程的進入。只有在當(dāng)前可
用資源計數(shù)值大于0時,被監(jiān)視的信號量內(nèi)核對象才會得到通知。
信號量的使
用特點使其更適
用于對Socket(套接字)程序中線程的同步。例如,網(wǎng)絡(luò)上的HTTP服務(wù)器要對同一時間內(nèi)訪問同一頁面的
用戶數(shù)加以限制,這時可以為沒一個
用戶對服務(wù)器的頁面請求設(shè)置一個線程,而頁面則是待保護的共享資源,通過使
用信號量對線程的同步作
用可以確保在任一時刻無論有多少
用戶對某一頁面進行訪問,只有不大于設(shè)定的最大
用戶數(shù)目的線程能夠進行訪問,而其他的訪問企圖則被掛起,只有在有
用戶退出對此頁面的訪問后才有可能進入。下面給出的示例代碼即展示了類似的處理過程:
// 信號量對象句柄 HANDLE hSemaphore; UINT ThreadProc15(LPVOID pParam) { // 試圖進入信號量關(guān)口 WaitForSingleObject(hSemaphore, INFINITE); // 線程任務(wù)處理 AfxMessageBox("線程一正在執(zhí)行!"); // 釋放信號量計數(shù) ReleaseSemaphore(hSemaphore, 1, NULL); return 0; } UINT ThreadProc16(LPVOID pParam) { // 試圖進入信號量關(guān)口 WaitForSingleObject(hSemaphore, INFINITE); // 線程任務(wù)處理 AfxMessageBox("線程二正在執(zhí)行!"); // 釋放信號量計數(shù) ReleaseSemaphore(hSemaphore, 1, NULL); return 0; } UINT ThreadProc17(LPVOID pParam) { // 試圖進入信號量關(guān)口 WaitForSingleObject(hSemaphore, INFINITE); // 線程任務(wù)處理 AfxMessageBox("線程三正在執(zhí)行!"); // 釋放信號量計數(shù) ReleaseSemaphore(hSemaphore, 1, NULL); return 0; } …… void CSample08View::OnSemaphore() { // 創(chuàng)建信號量對象 hSemaphore = CreateSemaphore(NULL, 2, 2, NULL); // 開啟線程 AfxBeginThread(ThreadProc15, NULL); AfxBeginThread(ThreadProc16, NULL); AfxBeginThread(ThreadProc17, NULL); } |

圖4 開始進入的兩個線程

圖5 線程二退出后線程三才得以進入
上述代碼在開啟線程前首先創(chuàng)建了一個初始計數(shù)和最大資源計數(shù)均為2的信號量對象hSemaphore。即在同一時刻只允許2個線程進入由hSemaphore保護的共享資源。隨后開啟的三個線程均試圖訪問此共享資源,在前兩個線程試圖訪問共享資源時,由于hSemaphore的當(dāng)前可
用資源計數(shù)分別為2和1,此時的hSemaphore是可以得到通知的,也就是說位于線程入口處的WaitForSingleObject()將立即返回,而在前兩個線程進入到保護區(qū)域后,hSemaphore的當(dāng)前資源計數(shù)減少到0,hSemaphore將不再得到通知,WaitForSingleObject()將線程掛起。直到此前進入到保護區(qū)的線程退出后才能得以進入。圖4和圖5為上述代脈的運行結(jié)果。從實驗結(jié)果可以看出,信號量始終保持了同一時刻不超過2個線程的進入。
在MFC中,通過CSemaphore類對信號量作了表述。該類只具有一個構(gòu)造函數(shù),可以構(gòu)造一個信號量對象,并對初始資源計數(shù)、最大資源計數(shù)、對象名和安全屬性等進行初始化,其原型如下:
| CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL ); |
在構(gòu)造了CSemaphore類對象后,任何一個訪問受保護共享資源的線程都必須通過CSemaphore從父類CSyncObject類繼承得到的Lock()和UnLock()成員函數(shù)來訪問或釋放CSemaphore對象。與前面介紹的幾種通過MFC類保持線程同步的方法類似,通過CSemaphore類也可以將前面的線程同步代碼進行改寫,這兩種使
用信號量的線程同步方法無論是在實現(xiàn)原理上還是從實現(xiàn)結(jié)果上都是完全一致的。下面給出經(jīng)MFC改寫后的信號量線程同步代碼:
// MFC信號量類對象 CSemaphore g_clsSemaphore(2, 2); UINT ThreadProc24(LPVOID pParam) { // 試圖進入信號量關(guān)口 g_clsSemaphore.Lock(); // 線程任務(wù)處理 AfxMessageBox("線程一正在執(zhí)行!"); // 釋放信號量計數(shù) g_clsSemaphore.Unlock(); return 0; } UINT ThreadProc25(LPVOID pParam) { // 試圖進入信號量關(guān)口 g_clsSemaphore.Lock(); // 線程任務(wù)處理 AfxMessageBox("線程二正在執(zhí)行!"); // 釋放信號量計數(shù) g_clsSemaphore.Unlock(); return 0; } UINT ThreadProc26(LPVOID pParam) { // 試圖進入信號量關(guān)口 g_clsSemaphore.Lock(); // 線程任務(wù)處理 AfxMessageBox("線程三正在執(zhí)行!"); // 釋放信號量計數(shù) g_clsSemaphore.Unlock(); return 0; } …… void CSample08View::OnSemaphoreMfc() { // 開啟線程 AfxBeginThread(ThreadProc24, NULL); AfxBeginThread(ThreadProc25, NULL); AfxBeginThread(ThreadProc26, NULL); } |