1. 程式人生 > >Win32下兩種用於C++的執行緒同步類(多執行緒實現加鎖解鎖)

Win32下兩種用於C++的執行緒同步類(多執行緒實現加鎖解鎖)

使用Win32提供的臨界區可以方便的實現執行緒鎖:

// 全域性:
CRITICAL_SECTION cs;
InitializeCriticalSection( & cs);
// 執行緒1:
EnterCriticalSection( & cs);
int a = s.a;
int b = s.b;
LeaveCriticalSection( & cs);
// 執行緒2:
EnterCriticalSection( & cs);
s.a ++ ;
s.b – ;
LeaveCriticalSection( & cs);
// 最後:
DeleteCriticalSection( & cs);

  程式碼中的臨界區變數(cs)就可以看作是變數s的鎖,當函式EnterCriticalSection返回時,當前執行緒就獲得了這把鎖,之後就是對變數的訪問了。訪問完成後,呼叫LeaveCriticalSection表示釋放這把鎖,允許其他執行緒繼續使用它。

  如果每當需要對一個變數進行加鎖時都需要做這些操作,顯得有些麻煩,而且變數cs與s只有邏輯上的鎖關係,在語法上沒有什麼聯絡,這對於鎖的管理帶來了不小的麻煩。程式設計師總是最懶的,可以想出各種偷懶的辦法來解決問題,例如讓被鎖的變數與加鎖的變數形成物理上的聯絡,使得鎖變數成為被鎖變數不可分割的一部分,這聽起來是個好主意。

  首先想到的是把鎖封閉在一個類裡,讓類的建構函式和解構函式來管理對鎖的初始化和鎖毀動作,我們稱這個鎖為“例項鎖”:

class InstanceLockBase
… {
CRITICAL_SECTION cs;
protected :
InstanceLockBase() … { InitialCriticalSection( & cs); }
~ InstanceLockBase() … { DeleteCriticalSection( & cs); }
} ;

  如果熟悉C++,看到這裡一定知道後面我要幹什麼了,對了,就是繼承,因為我把建構函式和解構函式都宣告為保護的(protected),這樣唯一的作用就是在子類裡使用它。讓我們的被保護資料從這個類繼承,那麼它們不就不可分割了嗎:

struct MyStruct: public InstanceLockBase
… { … } ;

  什麼?結構體還能從類繼承?當然,C++中結構體和類除了成員的預設訪問控制不同外沒有什麼不一樣,class能做的struct也能做。此外,也許你還會問,如果被鎖的是個簡單型別,不能繼承怎麼辦,那麼要麼用一個類對這個簡單型別進行封裝(記得Java裡有int和Integer嗎),要麼只好手工管理它們的聯絡了。如果被鎖類已經有了基類呢?沒關係,C++是允許多繼承的,多一個基類也沒什麼。

  現在我們的資料裡面已經包含一把鎖了,之後就是要新增加鎖和解鎖的動作,把它們作為InstanceLockBase類的成員函式再合適不過了:

class InstanceLockBase
… {
 CRITICAL_SECTION cs;
 void Lock() … { EnterCriticalSection( & cs); }
 void Unlock() … { LeaveCriticalSection( & cs); }
 …
} ;

  看到這裡可能會發現,我把Lock和Unlock函式都宣告為私有了,那麼如何訪問這兩個函式呢?是的,我們總是需要有一個地方來呼叫這兩個函式以實現加鎖和解鎖的,而且它們總應該成對出現,但C++語法本身沒能限制我們必須成對的呼叫兩個函式,如果加完鎖忘了解,那後果是嚴重的。這裡有一個例外,就是C++對於建構函式和解構函式的呼叫是自動成對的,對了,那就把對Lock和Unlock的呼叫專門寫在一個類的建構函式和解構函式中:

class InstanceLock
… {
 InstanceLockBase * _pObj;
 public :
  InstanceLock(InstanceLockBase * pObj)
  … {
   _pObj = pObj; // 這裡會儲存一份指向s的指標,用於解鎖
   if (NULL != _pObj)
   _pObj -> Lock(); // 這裡加鎖
  }
  ~ InstanceLock()
  … {
   if (NULL != _pObj)
   _pObj -> Unlock(); // 這裡解鎖
 }
} ;

  最後別忘了在類InstanceLockBase中把InstanceLock宣告為友元,使得它能正確訪問Lock和Unlock這兩個私有函式:

class InstanceLockBase
… {
 friend class InstanceLock;
 …
} ;

  好了,有了上面的基礎,現在對變數s的加解鎖管理變成了對InstanceLock的例項的生命週期的管理了。假如我們有一個函式ModifyS中要對s進行修改,那麼只要在函式一開始就宣告一個InstaceLock的例項,這樣整個函式就自動對s加鎖,一旦進入這個函式,其他執行緒就都不能獲得s的鎖了:

void ModifyS()
… {
 InstanceLock lock ( & s); // 這裡已經實現加鎖了
 // some operations on s
} // 一旦離開lock物件的作用域,自動解鎖

  如果是要對某函式中一部分程式碼加鎖,只要用一對大括號把它們括起來再宣告一個lock就可以了:


… {
 InstanceLock lock ( & s);
 // do something …
}

  好了,就是這麼簡單。下面來看一個測試。

  首先準備一個輸出函式,對我們理解程式有幫助。它會在輸出我們想輸出的內容同時打出行號和時間:

void Say( char * text)
… {
 static int count = 0 ;
 SYSTEMTIME st;
 ::GetLocalTime( & st);
 printf( ” d [d:d:d.d]%s ” , ++ count, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, text);
}

  當然,原則上當多執行緒都呼叫這個函式時應該對其靜態區域性變數count進行加鎖,這裡就省略了。

  我們宣告一個非常簡單的被鎖的型別,並生成一個例項:

class MyClass: public InstanceLockBase
… {} ;
MyClass mc;

  子執行緒的任務就是對這個物件加鎖,然後輸出一些資訊:

DWORD CALLBACK ThreadProc(LPVOID param)
… {
 InstanceLock il( & mc);
 Say( ” in sub thread, lock ” );
 Sleep( 2000 );
 Say( ” in sub thread, unlock ” );
 return 0 ;
}

  這裡會輸出兩條資訊,一是在剛剛獲得鎖的時間,二是在釋放鎖的時候,中間通過Sleep來延遲2秒。

  主執行緒負責開啟子執行緒,然後也對mc加鎖:

CreateThread( 0 , 0 , ThreadProc, 0 , 0 , 0 );
… {
 InstanceLock il( & mc);
 Say( ” in main thread, lock ” );
 Sleep( 3000 );
 Say( ” in main thread, lock ” );
}

  執行此程式,得到的輸出如下:

001 [13:43:23.781]in main thread, lock
002 [13:43:26.781]in main thread, lock
003 [13:43:26.781]in sub thread, lock
004 [13:43:28.781]in sub thread, unlock

  從其輸出的行號和時間可以清楚的看到兩個執行緒間的互斥:當主執行緒恰好首先獲得鎖時,它會延遲3秒,然後釋放鎖,之後子執行緒才得以繼續進行。這個例子也證明我們的類工作的很好。

  總結一下,要使用InstanceLock系列類,要做的就是:

  1、讓被鎖類從InstanceLockBase繼承

  2、所有要訪問被鎖物件的程式碼前面宣告InstanceLock的例項,並傳入被鎖物件的指標。

  附:完整原始碼:

pragma once

include < windows.h >

class InstanceLock;

class InstanceLockBase
… {
 friend class InstanceLock;

 CRITICAL_SECTION cs;

 void Lock()
 … {
  ::EnterCriticalSection( & cs);
 }

 void Unlock()
 … {
  ::LeaveCriticalSection( & cs);
 }

 protected :
 InstanceLockBase()
 … {
  ::InitializeCriticalSection( & cs);
 }

 ~ InstanceLockBase()
 … {
  ::DeleteCriticalSection( & cs);
 }
} ;

class InstanceLock
… {
 InstanceLockBase * _pObj;
 public :
  InstanceLock(InstanceLockBase * pObj)
  … {
   _pObj = pObj;
   if (NULL != _pObj)
    _pObj -> Lock();
  }

 ~ InstanceLock()
 … {
  if (NULL != _pObj)
   _pObj -> Unlock();
 }
} ;