線程需要相互連信,當多個線程訪問共用資源的時候;當一個線程結束一個任務,然後要通知另一個線程的時候,我們需要進行線程同步的控制。
要讓共用資源不被破壞,或者讓另一個線程能夠在適當的時候被調度,這就是線程同步需要解決的問題。
Windows Vista提供了多種線程同步的方法,這裡主要介紹在使用者模式下的線程同步。
1、互鎖函數族:Interlocked*函數群(*表示省略後面的字元,即以Interlocked開頭)。
2、關鍵程式碼片段:Criticle Sections
3、讀寫鎖:Slim Read-Write Locks
4、狀態變數:Condition Variable
互瑣函數族
本質上是對一個變數進行原子性的修改,在修改的時候不受到其他線程的幹擾。也就是說,“讀數”、“修改”、“寫回”這3個操作是一個整體,中間沒有停頓,不可分割。
考慮如下代碼:
long g_x = 0;
DWORD WINAPI ThreadFunc1(PVOID pvParam) {
g_x++;
return(0);
}
DWORD WINAPI ThreadFunc2(PVOID pvParam) {
g_x++;
return(0);
}
兩個線程共用一個全域變數,當這兩個線程都執行完成之後,g_x會變成2,但是事實一定是這樣嗎?
“g_x++”這條語句包含了三個過程:1、取得g_x的值,送到寄存器中;2、把寄存器中的值加1;3、將得到的新的值寫回到原來g_x所對應的儲存單元中。
設想如下情形:線程1首先取得g_x的值,送到EAX寄存器中,然後對EAX中的數值加1,然後由於某種原因暫停執行。這個時候,CPU分配給了線程2,然後線程2取得g_x的值,也把它送到EAX寄存器中,這樣EAX裡面原來的值就被這個時候的g_x(數值為0)覆蓋了,然後線程2將EAX中的數值加1,當線程2寫回的時候,把1寫回到了g_x中。然後線程1恢複執行,將資料從EAX中寫回,它寫回的也是1,而不是2!
可見,這兩個線程相互之間會干擾對方的行動,從而可能破壞共用的資料。上面討論的這種情況,是典型的“丟失修改”。同時,還可能有“讀髒資料”、“不可重複讀取”等同步問題。
可以使用“互鎖函數族”解決上述問題,你可以調用下面兩個函數來代替g_x++:
LONG InterlockedExchangeAdd(
PLONG volatile plAddend,
LONG lIncrement);
LONGLONG InterlockedExchangeAdd64(
PLONGLONG volatile pllAddend,
LONGLONG llIncrement);
這兩個函數第一參數可以理解為“被加數”(其實傳遞的是一個數值的地址),第二個參數可以理解為“加數”。就相當於執行“*plAddend = *plAddend + lIncrement”。唯一的區別“取數”、“修改”、“寫回”成了一個統一的整體,是一個不可分割原子操作。
你可以把上面代碼中的“g_x++”換成“InterlockedExchangeAdd(&g_x, 1)”。
你也可以將第二個參數寫為一個負數,就相當於執行減運算。
還有一些互鎖函數:
LONG InterlockedExchange(
PLONG volatile plTarget,
LONG lValue);
LONGLONG InterlockedExchange64(
PLONGLONG volatile plTarget,
LONGLONG lValue);
PVOID InterlockedExchangePointer(
PVOID* volatile ppvTarget,
PVOID pvValue);
這三個函數將第一個參數(一個指標)指向的資料替換為第二個參數中的值。這三個函數都返回第一個參數指向資料的原始值。
實現迴圈鎖的時候,InterlockedExchange函數比較有用。
BOOL g_fResourceInUse = FALSE; //全域變數,指明資源是否正在被訪問
void Func1() { //線程函數
// 查看訪問標誌,如果當前值為TRUE表示正在被另一個線程使用,本線程等待
while (InterlockedExchange (&g_fResourceInUse, TRUE) == TRUE)
Sleep(0);
... // 訪問資源
InterlockedExchange(&g_fResourceInUse, FALSE);// 將訪問標誌設為FALSE
}
還有幾個使用者比較並修改值或指標的互鎖函數:
PVOID InterlockedCompareExchange(
PLONG plDestination,
LONG lExchange,
LONG lComparand);
LONGLONG InterlockedCompareExchange64(
LONGLONG pllDestination,
LONGLONG llExchange,
LONGLONG llComparand);
PVOID InterlockedCompareExchangePointer(
PVOID* ppvDestination,
PVOID pvExchange,
PVOID pvComparand);
這三個函數負責執行一個原子的“比較和修改”操作。
第一個參數:指向需要被修改的值的指標;第二個參數:修改的值;第三個參數:被比較的值。
拿InterlockedCompareExchange為例。
函數比較*plDestination(第一個參數指向的值)和lComparand(第三個)的值是否相等,如果相等,則將plDestination指向的值修改為lExchange,否則,就不進行修改。最後,函數返回plDestination代表的原來的值。
還有一些互鎖函數:
LONG InterlockedIncrement(PLONG plAddend); //將參數指向的值加1
LONG InterlockedDecrement(PLONG plAddend); //將參數指向的值減1
在Windows XP及其以上版本系統中,除了可以原子地操縱整型和布爾型的值之外,還可以通過一系列函數來操縱一個堆棧,稱為“Interlocked Singly Linked List”(互鎖單鏈表)。每一個操作,比如壓棧、出棧,都以原子的方式進行操作。
下表裡出了一些作用在“互鎖單鏈表”上的函數:
函數 |
描述 |
InitializeSListHead |
建立一個空的堆棧 |
InterlockedPushEntrySList |
壓棧 |
InterlockedPopEntrySList |
出粘 |
InterlockedFlushSList |
清空堆棧 |
QueryDepthSList |
查詢堆棧中元素個數 |