《Windows via C/C++》學習筆記 —— 使用者模式的“線程同步”之“互鎖函數族”

來源:互聯網
上載者:User

  線程需要相互連信,當多個線程訪問共用資源的時候;當一個線程結束一個任務,然後要通知另一個線程的時候,我們需要進行線程同步的控制。

  要讓共用資源不被破壞,或者讓另一個線程能夠在適當的時候被調度,這就是線程同步需要解決的問題。

 

  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

查詢堆棧中元素個數

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.