信號量
信號量是維護0到指定最大值之間的同步對象。信號量狀態(tài)在其計數(shù)大于0時是有信號的,而其計數(shù)是0時是無信號的。信號量對象在控制上可以支持有限數(shù)量共享資源的訪問。
信號量的特點和用途可用下列幾句話定義:
?。?)如果當前資源的數(shù)量大于0,則信號量有效;
?。?)如果當前資源數(shù)量是0,則信號量無效;
?。?)系統(tǒng)決不允許當前資源的數(shù)量為負值;
?。?)當前資源數(shù)量決不能大于最大資源數(shù)量。
創(chuàng)建信號量
HANDLE CreateSemaphore ( PSECURITY_ATTRIBUTE psa, LONG lInitialCount, //開始時可供使用的資源數(shù) LONG lMaximumCount, //最大資源數(shù) PCTSTR pszName); |
釋放信號量
通過調用ReleaseSemaphore函數(shù),線程就能夠對信標的當前資源數(shù)量進行遞增,該函數(shù)原型為:
BOOL WINAPI ReleaseSemaphore( HANDLE hSemaphore, LONG lReleaseCount, //信號量的當前資源數(shù)增加lReleaseCount LPLONG lpPreviousCount ); |
打開信號量
和其他核心對象一樣,信號量也可以通過名字跨進程訪問,打開信號量的API為:
HANDLE OpenSemaphore ( DWORD fdwAccess, BOOL bInherithandle, PCTSTR pszName ); |
互鎖訪問 當必須以原子操作方式來修改單個值時,互鎖訪問函數(shù)是相當有用的。所謂原子訪問,是指線程在訪問資源時能夠確保所有其他線程都不在同一時間內訪問相同的資源。
請看下列代碼:
int globalVar = 0;
DWORD WINAPI ThreadFunc1(LPVOID n) { globalVar++; return 0; } DWORD WINAPI ThreadFunc2(LPVOID n) { globalVar++; return 0; } |
運行ThreadFunc1和ThreadFunc2線程,結果是不可預料的,因為globalVar++并不對應著一條機器指令,我們看看globalVar++的反
匯編代碼:
00401038 mov eax,[globalVar (0042d3f0)] 0040103D add eax,1 00401040 mov [globalVar (0042d3f0)],eax |
在"mov eax,[globalVar (0042d3f0)]" 指令與"add eax,1" 指令以及"add eax,1" 指令與"mov [globalVar (0042d3f0)],eax"指令之間都可能發(fā)生線程切換,使得程序的執(zhí)行后globalVar的結果不能確定。我們可以使用Interlocked
ExchangeAdd函數(shù)解決這個問題:
int globalVar = 0;
DWORD WINAPI ThreadFunc1(LPVOID n) { InterlockedExchangeAdd(&globalVar,1); return 0; } DWORD WINAPI ThreadFunc2(LPVOID n) { InterlockedExchangeAdd(&globalVar,1); return 0; } |
InterlockedExchangeAdd保證對變量globalVar的訪問具有"原子性"。互鎖訪問的控制速度非???,調用一個互鎖函數(shù)的CPU周期通常小于50,不需要進行用戶方式與內核方式的切換(該切換通常需要運行1000個CPU周期)。
互鎖訪問函數(shù)的缺點在于其只能對單一變量進行原子訪問,如果要訪問的資源比較復雜,仍要使用臨界區(qū)或互斥。
可等待定時器
可等待定時器是在某個時間或按規(guī)定的間隔時間發(fā)出自己的信號通知的內核對象。它們通常用來在某個時間執(zhí)行某個操作。
創(chuàng)建可等待定時器
HANDLE CreateWaitableTimer( PSECURITY_ATTRISUTES psa, BOOL fManualReset,//人工重置或自動重置定時器 PCTSTR pszName); |
設置可等待定時器
可等待定時器對象在非激活狀態(tài)下被創(chuàng)建,程序員應調用 SetWaitableTimer函數(shù)來界定定時器在何時被激活:
BOOL SetWaitableTimer( HANDLE hTimer, //要設置的定時器 const LARGE_INTEGER *pDueTime, //指明定時器第一次激活的時間 LONG lPeriod, //指明此后定時器應該間隔多長時間激活一次 PTIMERAPCROUTINE pfnCompletionRoutine, PVOID PvArgToCompletionRoutine, BOOL fResume); |
取消可等待定時器
BOOl Cancel WaitableTimer( HANDLE hTimer //要取消的定時器 ); |
打開可等待定時器
作為一種內核對象,WaitableTimer也可以被其他進程以名字打開:
HANDLE OpenWaitableTimer ( DWORD fdwAccess, BOOL bInherithandle, PCTSTR pszName ); |
實例 下面給出的一個程序可能發(fā)生死鎖現(xiàn)象:
#include <Windows.h> #include <stdio.h> CRITICAL_SECTION cs1, cs2; long WINAPI ThreadFn(long); main() { long iThreadID; InitializeCriticalSection(&cs1); InitializeCriticalSection(&cs2); CloseHandle(CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFn, NULL, 0,&iThreadID)); while (TRUE) { EnterCriticalSection(&cs1); printf("\n線程1占用臨界區(qū)1"); EnterCriticalSection(&cs2); printf("\n線程1占用臨界區(qū)2");
printf("\n線程1占用兩個臨界區(qū)");
LeaveCriticalSection(&cs2); LeaveCriticalSection(&cs1);
printf("\n線程1釋放兩個臨界區(qū)"); Sleep(20); }; return (0); }
long WINAPI ThreadFn(long lParam) { while (TRUE) { EnterCriticalSection(&cs2); printf("\n線程2占用臨界區(qū)2"); EnterCriticalSection(&cs1); printf("\n線程2占用臨界區(qū)1");
printf("\n線程2占用兩個臨界區(qū)");
LeaveCriticalSection(&cs1); LeaveCriticalSection(&cs2);
printf("\n線程2釋放兩個臨界區(qū)"); Sleep(20); }; } |
運行這個程序,在中途一旦發(fā)生這樣的輸出:
線程1占用臨界區(qū)1
線程2占用臨界區(qū)2
或
線程2占用臨界區(qū)2
線程1占用臨界區(qū)1
或
線程1占用臨界區(qū)2
線程2占用臨界區(qū)1
或
線程2占用臨界區(qū)1
線程1占用臨界區(qū)2
程序就"死"掉了,再也運行不下去。因為這樣的輸出,意味著兩個線程相互等待對方釋放臨界區(qū),也即出現(xiàn)了死鎖。
如果我們將線程2的控制函數(shù)改為:
long WINAPI ThreadFn(long lParam) { while (TRUE) { EnterCriticalSection(&cs1); printf("\n線程2占用臨界區(qū)1"); EnterCriticalSection(&cs2); printf("\n線程2占用臨界區(qū)2");
printf("\n線程2占用兩個臨界區(qū)");
LeaveCriticalSection(&cs1); LeaveCriticalSection(&cs2);
printf("\n線程2釋放兩個臨界區(qū)"); Sleep(20); }; } |
再次運行程序,死鎖被消除,程序不再擋掉。這是因為我們改變了線程2中獲得臨界區(qū)1、2的順序,消除了線程1、2相互等待資源的可能性。
由此我們得出結論,在使用線程間的同步機制時,要特別留心死鎖的發(fā)生。