信 號量(Semaphore
)內核對象對線程的同步方式與前面幾種方法不同,它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最
大線程數目。在用CreateSemaphore
()
創建信號量時即要同時指出允許的最大資源計數和當前可用資源計數。一般是將當前可用資源計數設置爲最
大資源計數,每增加一個線程對共享資源的訪問,當前可用資源計數就會減1
,只要當前可用資源計數是大於0
的,就可以發出信號量信號。但是當前可用計數減小 到0
時則說明當前佔用資源的線程數已經達到了所允許的最大數目,不能在允許其他線程的進入,此時的信號量信號將無法發出。線程在處理完共享資源後,應在離
開的同時通過ReleaseSemaphore
()函數將當前可用資源計數加1
。在任何時候當前可用資源計數決不可能大於最大資源計數。
信號量是通過計數來對線程訪問資源進行控制的,而實際上信號量確實也被稱作Dijkstra
計數器。
使用信號量內核對象進行線程同步主要會用到CreateSemaphore
()、OpenSemaphore
()、 ReleaseSemaphore
()、WaitForSingleObject
()和WaitForMultipleObjects
()等函數。其
中,CreateSemaphore
()用來創建一個信號量內核對象,其函數原型爲:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, //
安全屬性指針
LONG lInitialCount, //
初始計數
LONG lMaximumCount, //
最大計數
LPCTSTR lpName //
對象名指針
);
參數lMaximumCount
是一個有符號32
位值,定義了允許的最大資源計數,最大取值不能超過4294967295
。lpName
參數可以爲創建的 信號量定義一個名字,由於其創建的是一個內核對象,因此在其他進程中可以通過該名字而得到此信號量。OpenSemaphore
()函數即可用來根據信號 量名打開在其他進程中創建的信號量,函數原型如下:
HANDLE OpenSemaphore(
DWORD dwDesiredAccess, //
訪問標誌
BOOL bInheritHandle, //
繼承標誌
LPCTSTR lpName //
信號量名
);
在線程離開對共享資源的處理時,必須通過ReleaseSemaphore
()來增加當前可用資源計數。否則將會出現當前正在處理共享資源的實際線程數並
沒有達到要限制的數值,而其他線程卻因爲當前可用資源計數爲0
而仍無法進入的情況。ReleaseSemaphore
()的函數原型爲:
BOOL ReleaseSemaphore(
HANDLE hSemaphore, //
信號量句柄
LONG lReleaseCount, //
計數遞增數量
LPLONG lpPreviousCount //
先前計數
);
該函數將lReleaseCount
中的值添加給信號量的當前資源計數,一般將lReleaseCount
設置爲1
,如果需要也可以設置其他的值。 WaitForSingleObject
()和WaitForMultipleObjects
()主要用在試圖進入共享資源的線程函數入口處,主要用來判
斷信號量的當前可用資源計數是否允許本線程的進入。只有在當前可用資源計數值大於0
時,被監視的信號量內核對象纔會得到通知。
信號量的使用特點使其更適用於對Socket
(套接字)程序中線程的同步。例如,網絡上的HTTP
服務器要對同一時間內訪問同一頁面的用戶數加以限制,這 時可以爲每一個用戶對服務器的頁面請求設置一個線程,而頁面則是待保護的共享資源,通過使用信號量對線程的同步作用可以確保在任一時刻無論有多少用戶對某
一頁面進行訪問,只有不大於設定的最大用戶數目的線程能夠進行訪問,而其他的訪問企圖則被掛起,只有在有用戶退出對此頁面的訪問後纔有可能進入。下面給出 的示例代碼即展示了類似的處理過程:
//
信號量對象句柄
HANDLE hSemaphore;
UINT ThreadProc15(LPVOID pParam)
{
//
試圖進入信號量關口
WaitForSingleObject(hSemaphore, INFINITE);
//
線程任務處理
AfxMessageBox("
線程一正在執行!");
//
釋放信號量計數
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
UINT ThreadProc16(LPVOID pParam)
{
//
試圖進入信號量關口
WaitForSingleObject(hSemaphore, INFINITE);
//
線程任務處理
AfxMessageBox("
線程二正在執行!");
//
釋放信號量計數
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
UINT ThreadProc17(LPVOID pParam)
{
//
試圖進入信號量關口
WaitForSingleObject(hSemaphore, INFINITE);
//
線程任務處理
AfxMessageBox("
線程三正在執行!");
//
釋放信號量計數
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
……
void CSample08View::OnSemaphore()
{
//
創建信號量對象
hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);
//
開啓線程
AfxBeginThread(ThreadProc15, NULL);
AfxBeginThread(ThreadProc16, NULL);
AfxBeginThread(ThreadProc17, NULL);
}
上述代碼在開啓線程前首先創建了一個初始計數和最大資源計數均爲2
的信號量對象hSemaphore
。即在同一時刻只允許2
個線程進入由 hSemaphore
保護的共享資源。隨後開啓的三個線程均試圖訪問此共享資源,在前兩個線程試圖訪問共享資源時,由於hSemaphore
的當前可用資 源計數分別爲2
和1
,此時的hSemaphore
是可以得到通知的,也就是說位於線程入口處的WaitForSingleObject
()將立即返回,而 在前兩個線程進入到保護區域後,hSemaphore
的當前資源計數減少到0
,hSemaphore
將不再得到通 知,WaitForSingleObject
()將線程掛起。直到此前進入到保護區的線程退出後才能得以進入。圖4
和圖5
爲上述代脈的運行結果。從實驗結 果可以看出,信號量始終保持了同一時刻不超過2
個線程的進入。
在MFC
中,通過CSemaphore
類對信號量作了表述。該類只具有一個構造函數,可以構造一個信號量對象,並對初始資源計數、最大資源計數、對象名和安全屬性等進行初始化,其原型如下:
CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName =
NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );
在構造了CSemaphore
類對象後,任何一個訪問受保護共享資源的線程都必須通過CSemaphore
從父類CSyncObject
類繼承得到的 Lock
()和UnLock
()成員函數來訪問或釋放CSemaphore
對象。與前面介紹的幾種通過MFC
類保持線程同步的方法類似,通過 CSemaphore
類也可以將前面的線程同步代碼進行改寫,這兩種使用信號量的線程同步方法無論是在實現原理上還是從實現結果上都是完全一致的。下面給
出經MFC
改寫後的信號量線程同步代碼:
// MFC
信號量類對象
CSemaphore g_clsSemaphore(2, 2);
UINT ThreadProc24(LPVOID pParam)
{
//
試圖進入信號量關口
g_clsSemaphore.Lock();
//
線程任務處理
AfxMessageBox("
線程一正在執行!");
//
釋放信號量計數
g_clsSemaphore.Unlock();
return 0;
}
UINT ThreadProc25(LPVOID pParam)
{
//
試圖進入信號量關口
g_clsSemaphore.Lock();
//
線程任務處理
AfxMessageBox("
線程二正在執行!");
//
釋放信號量計數
g_clsSemaphore.Unlock();
return 0;
}
UINT ThreadProc26(LPVOID pParam)
{
//
試圖進入信號量關口
g_clsSemaphore.Lock();
//
線程任務處理
AfxMessageBox("
線程三正在執行!");
//
釋放信號量計數
g_clsSemaphore.Unlock();
return 0;
}
……
void CSample08View::OnSemaphoreMfc()
{
//
開啓線程
AfxBeginThread(ThreadProc24, NULL);
AfxBeginThread(ThreadProc25, NULL);
AfxBeginThread(ThreadProc26, NULL);
}
|