翻譯自codeproject上面的一篇文章,題目是:如何建立一個簡單的c++同步鎖框架php
介紹html
背景c++
臨界區 & 互斥 & 信號git
臨界區github
互斥windows
信號安全
更多信息多線程
創建鎖框架的目的框架
BaseLock類函數
臨界區類
構造/拷貝構造
析構
Lock/TryLock
TryLockFor
解鎖
信號量類
構造/拷貝構造
析構
Lock/TryLock/TryLockFor
解鎖
釋放
互斥類
構造/拷貝構造
析構
Lock/ TryLock/TryLockFor
解鎖
無鎖類
實現
自動釋放鎖類
實現
例子
聲明同步對象
聲明帶初始計數的信號量對象
聲明跨進程的互斥/信號量對象
同步示例1 (手動加鎖)
同步示例2 (自動加鎖)
同步示例3 (TryLock/TryLockFor)
更多的示例
結論
參考
開發多線程/多進程應用時, 同步是一個很大的問題. 你須要讓你的程序同時知足單線程和多線程/多進程的應用場合,固然如何管理同步又是另外一個話題了。利用鎖(同步)機制不會自動幫組你解決上訴問題,可是有助於你找到解決問題的方法。這篇文章向你解釋瞭如何構建簡單鎖框架,如何在你的應用中運用它。可能不少人已經開發了本身的鎖框架,或者利用成熟的庫好比 boost library或者 Intel TBB。可是有時候它們對於你的應用來講可能過於臃腫,也可能並非你想要的。經過閱讀本文,你可能構建符合你本身的鎖框架。
本文將解釋下面這些東西:
BaseLock類
Critical Section類
Semaphore類
Mutex類
NoLock類
Auto-release Lock
示例
提示: 本文既不是要講解高級的鎖框架,也不是要講解同步基元的區別。正如題目所說,本文只會告訴你:如何構建一個簡單的鎖框架,你能夠根據本文的框架結合你本身的須要,運用或者修改它。
這裏你必須瞭解如下幾個同步基元的概念和他們之間的區別:
Critical Section
Semaphore
Mutex
若是你對MFC中的同步類(CSingleLock/CMultiLock, CCriticalSection, CSemaphore, and CMutex)有所瞭解,這將有助於你理解並運用這些類來代替windows函數庫去實現一個鎖框架。
下面這段主要來自於這篇文章 "Synchronization in Multithreaded Applications with MFC" ,由Arman S所寫,我只是引用其中的內容,全部權爲他本人全部。
臨界區工做於用戶模式,若是須要能夠進入內核模式。若是一個線程運行一段位於臨界區的代碼,它首先會產生一個自旋區並在一段時間的等待以後進入內核模式並等待臨界區對象。實際上臨界區由自旋計數和信號量組成。前者用於用戶態模式下的等待,後者用於內核模式下的等待(睡眠)。在win32 API中,結構體CRITICAL_SECTION
表示了一個臨界區。在MFC
中,用類CCriticalSection
表示。概念上一個臨界區表示一段可運行的代碼,並保證這段代碼在執行過程當中不會被中斷打斷。這樣作是爲了保證一個單線程在訪問共享資源時,擁有絕對佔有權。
互斥與臨界區同樣,也是用於同步訪問時保護共享資源。互斥在內核中實現,所以能夠進入內核模式。互斥不只能夠用於線程之間,也能夠用於進程之間。通常用於不一樣進程之間時,它都擁有一個唯一的名字,咱們稱這種互斥爲命名互斥。
爲了限制訪問共享資源的線程數量,咱們須要使用信號量。信號量是內核對象,它有一個計數變量用於表示當前正在使用共享資源的線程數。舉個例子,下面的代碼用MFC的CSemaphore類建立了一個信號量,它能保證在設定的時間週期(這個值有第一個參數設定)內可以同時訪問共享資源的最大線程數量,而且初始化完成後的時刻沒有線程對資源擁有全部權(有第二個參數設定)。
HANDLE g_sem = CreateSemaphore(NULL,5,5,NULL);
若是想了解更多,請閱讀Arms S的這篇文章 here
在windows開發中,最多見的同步基元是Critical Section, Semaphore, 和Mutex. 對於初學者他們可能常用這些同步基元,卻很難去了解同步概念自己。所以創建一個簡單的鎖框架的出發點是使得不一樣基元擁有統一的接口和調用方式, 可是按照它們原始的機制去工做,這樣更容易理解。
雖然不一樣的同步基元做用不同,可是仍然有一些基本的功能共同點。
Lock
TryLock
TryLockFor (試加鎖一段時間)
Unlock
可能有人會想到更多的共同點,可是你能夠根據你的需求選擇構建你本身的鎖框架,這裏我只是用這4個函數來簡化咱們想要說明的問題。咱們的 BaseLock
類是一個純虛類,以下所示:
// BaseLock.h class BaseLock { public: BaseLock(); virtual ~BaseLock(); virtual bool Lock() =0; virtual long TryLock()=0; virtual long TryLockFor(const unsigned int dwMilliSecond)=0; virtual void Unlock()=0; };
注意一點:對於互斥而言,Lock函數返回布爾型,對於其它的基元,這個函數老是返回真。
由於CRITICAL_SECTION
結構體已經存在了,因此這裏咱們將這個類命名爲CriticalSectionEx
。這個類繼承自BaseLock
類,也是CRITICAL_SECTION
對象的一個接口類。與BaseLock
類類似,咱們定義以下:
// CriticalSectionEx.h #include "BaseLock.h" class CriticalSectionEx :public BaseLock { public: CriticalSectionEx(); CriticalSectionEx(const CriticalSectionEx& b); virtual ~CriticalSectionEx(); CriticalSectionEx & operator=(const CriticalSectionEx&b) { return *this; } virtual bool Lock(); virtual long TryLock(); virtual long TryLockFor(const unsigned int dwMilliSecond); virtual void Unlock(); private: CRITICAL_SECTION m_criticalSection; int m_lockCounter; };
注意,
賦值操做符重載函數"operator="
返回自身,是由於我不想讓成員對象CRITICAL_SECTION
被改變,若是"operator="
沒有被重載,它會自動調用拷貝函數,這樣CRITICAL_SECTION
對象的值就會被修改替代。另外爲何沒有將賦值操做符函數設爲私有?緣由在於當一個類好比說A
試圖從其它類拷貝時,訪問私有函數會產生編譯錯誤。
另外,臨界區在重入時,若是進入和離開次數不對等將產生未定義行爲
, 因此咱們加入了成員變量m_lockCounter
,用來跟蹤
locks/unlocks的數量。
下面的5個函數是臨界區使用時的經常使用函數:
InitializeCriticalSection
DeleteCriticalSection
EnterCriticalSection
LeaveCriticalSection
TryEnterCriticalSection
更多信息請參考MSDN。這5個函數將合併以下:
// CriticalSectionEx.cpp CriticalSectionEx::CriticalSectionEx() :BaseLock() { InitializeCriticalSection(&m_criticalSection); m_lockCounter=0; } CriticalSectionEx::CriticalSectionEx(const CriticalSectionEx& b):BaseLock() { InitializeCriticalSection(&m_criticalSection); m_lockCounter=0; }
// CriticalSectionEx.cpp CriticalSectionEx::~CriticalSectionEx() { assert(m_lockCounter==0); DeleteCriticalSection(&m_criticalSection); }
若是CRITICAL_SECTION
對象被初始化後,它必需要釋放,因此將
DeleteCriticalSection
放在析構函數裏,CriticalSectionEx
析構時CRITICAL_SECTION
將被自動釋放。注意若是m_lockCounter
不爲
0,這意味着加鎖和解鎖次數不相同,這將致使未定義行爲。
// CriticalSectionEx.cpp bool CriticalSectionEx::Lock() { EnterCriticalSection(&m_criticalSection); m_lockCounter++ return true; } long CriticalSectionEx::TryLock() { long ret=TryEnterCriticalSection(&m_criticalSection); if(ret) m_lockCounter++; return ret; }
這是很是直觀的實現方式。不解釋了!
// CriticalSectionEx.cpp long CriticalSectionEx::TryLockFor(const unsigned int dwMilliSecond) { long ret=0; if(ret=TryEnterCriticalSection(&m_criticalSection)) { m_lockCounter++; return ret; } else { unsigned int startTime,timeUsed; unsigned int waitTime=dwMilliSecond; startTime=GetTickCount(); while(WaitForSingleObject(m_criticalSection.LockSemaphore,waitTime)==WAIT_OBJECT_0) { if(ret=TryEnterCriticalSection(&m_criticalSection)) { m_lockCounter++; return ret; } timeUsed=GetTickCount()-startTime; waitTime=waitTime-timeUsed; startTime=GetTickCount(); } return 0; } }
注意,原始的TryEnterCriticalSection
函數沒有時間參數
(根據MSDN的說法是爲了防止死鎖)。所以我用一點技巧來模擬必定時間段內的TryLock機制,使用的時候你須要注意一點,由於微軟並無爲CRITICAL_SECTION
對象實現這個接口。這裏並不保證進入臨界區的順序。
Unlock
// CriticalSectionEx.cpp void CriticalSectionEx::Unlock() { assert(m_lockCounter>=0); LeaveCriticalSection(&m_criticalSection); }
若是獲取到CRITICAL_SECTION
對象,釋放時經過調用"LeaveCriticalSection"
來解鎖表示離開臨界區。主要到m_lockCounter
必須大於等於
0,不然表示解鎖次數大於加鎖次數。
咱們的信號量類也繼承自BaseLock
類,是Semaphore
對象的接口類
(其實是一個句柄
)。以下:
// Semaphore.h #include "BaseLock.h" class Semaphore :public BaseLock { public: Semaphore(unsigned int count=1,const TCHAR *semName=_T(""), LPSECURITY_ATTRIBUTES lpsaAttributes = NULL); Semaphore(const Semaphore& b); virtual ~Semaphore(); Semaphore & operator=(const Semaphore&b) { return *this; } virtual bool Lock(); virtual long TryLock(); virtual long TryLockFor(const unsigned int dwMilliSecond); virtual void Unlock(); long Release(long count, long * retPreviousCount); private: /// Actual Semaphore HANDLE m_sem; /// Creation Info LPSECURITY_ATTRIBUTES m_lpsaAttributes; /// Semaphore Flag unsigned int m_count; };
注意, 信號量類的構造函數的參數有2
個:semName
和lpsaAttributes
,而
BaseLock和 CriticalSectionEx的構造函數沒有。另外它的構造函數調用時能夠不加參數,由於有默認參數,這幾個參數的意義以下:
下面的函數是信號量的4個經常使用操做函數:
CreateSemaphore
CloseHandle
WaitForSingleObject
ReleaseSemaphore
更多信息可參考MSDN,這4個函數合併以下:
// Semaphore.cpp Semaphore::Semaphore(unsigned int count,const TCHAR *semName, LPSECURITY_ATTRIBUTES lpsaAttributes) :BaseLock() { m_lpsaAttributes=lpsaAttributes; m_count=count; m_sem=CreateSemaphore(lpsaAttributes,count,count,semName); } Semaphore::Semaphore(const Semaphore& b) :BaseLock() { m_lpsaAttributes=b.m_lpsaAttributes; m_count=b.m_count; m_sem=CreateSemaphore(m_lpsaAttributes,m_count,m_count,NULL); }
這裏的拷貝構造函數,複製了另外一個類的安全屬性和計數值,而後建立一個新的信號量對象。
// Semaphore.cpp Semaphore::~Semaphore() { CloseHandle(m_sem); }
// Semaphore.cpp bool Semaphore::Lock() { WaitForSingleObject(m_sem,INIFINITE); return true; } long Semaphore::TryLock() { long ret=0; if(WaitForSingleObject(m_sem,0) == WAIT_OBJECT_0 ) ret=1; return ret; } long Semaphore::TryLockFor(const unsigned int dwMilliSecond) { long ret=0; if( WaitForSingleObject(m_sem,dwMilliSecond) == WAIT_OBJECT_0) ret=1; return ret; }
// Semaphore.cpp void Semaphore::Unlock() { ReleaseSemaphore(m_sem,1,NULL); }
如Ahmed Charfeddine建議的那樣,對於信號量的Unlock函數最好能夠接受一個表示釋放計數的參數並返回成功或失敗結果。這是信號量比較特別的地方,你能夠從新寫一個Release函數進行擴展。
// Semaphore.cpp void Semaphore::Release(long count, long * retPreviousCount) { return ReleaseSemaphore(m_sem,count,retPreviousCount); } //用法 BaseLock *lock = new Semaphore(10); ... BOOL ret = dynamic_cast<Semaphore*>(lock)->Release(5); // OR Semaphore lock(10); ... BOOL ret = lock.Release(5);
8 Mutex Class
互斥類繼承自BaseLock
,是互斥對象的一個接口類。
// Mutex.h #include "BaseLock.h" class Mutex :public BaseLock { public: Mutex(const TCHAR *mutexName=NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL); Mutex(const Mutex& b); virtual ~Mutex(); Mutex & operator=(const Mutex&b) { return *this; } virtual bool Lock(); virtual long TryLock(); virtual long TryLockFor(const unsigned int dwMilliSecond); virtual void Unlock(); bool IsMutexAbandoned() { return m_isMutexAbandoned; ) private: /// Mutex HANDLE m_mutex; /// Creation Security Info LPSECURITY_ATTRIBUTES m_lpsaAttributes; /// Flag for whether this mutex is abandoned or not. bool m_isMutexAbandoned; };
注意,
互斥量能夠被捨棄,可是咱們不想單獨爲了互斥量改變統一的接口,因此我增長了一個函數IsMutexAbandoned
用來檢查此互斥量是否在加鎖失敗時被捨棄了。
互斥量的經常使用函數以下:
CreateMutex
CloseHandle
WaitForSingleObject
ReleaseMutex
更多信息請參考MSDN.
// Mutex.cpp Mutex::Mutex(const TCHAR *mutexName, LPSECURITY_ATTRIBUTES lpsaAttributes) :BaseLock() { m_isMutexAbandoned=false; m_lpsaAttributes=lpsaAttributes; m_mutex=CreateMutex(lpsaAttributes,FALSE,mutexName); } Mutex::Mutex(const Mutex& b) { m_isMutexAbandoned=false; m_lpsaAttributes=b.m_lpsaAttributes; m_mutex=CreateMutex(m_lpsaAttributes,FALSE,NULL); }
// Mutex.cpp Mutex::~Mutex() { CloseHandle(m_mutex); }
// Mutex.cpp void Mutex::Lock() { bool returnVal = true; unsigned long res=WaitForSingleObject(m_mutex,INIFINITE); if(res=WAIT_ABANDONED) { m_isMutexAbandoned=true; returnVal=false; } return returnVal; } long Mutex::TryLock() { long ret=0; unsigned long mutexStatus= WaitForSingleObject(m_mutex,0); if(mutexStatus== WAIT_OBJECT_0) ret=1; else if(mutexStatus==WAIT_ABANDONED) m_isMutexAbandoned=true; return ret; } long Mutex::TryLockFor(const unsigned int dwMilliSecond) { long ret=0; unsigned long mutexStatus= WaitForSingleObject(m_mutex,dwMilliSecond); if(mutexStatus==WAIT_OBJECT_0) ret=1; else if(mutexStatus==WAIT_ABANDONED) m_isMutexAbandoned=true; return ret; }
調用Lock
函數時,無限等待直到獲取互斥對象,當
WaitForSingleObject函數返回時便自動獲取了互斥對象。調用TryLock
時,不等待直接試圖獲取互斥對象,而
TryLockFor會在給定之間內試着獲取互斥對象,更多參考信息請看 MSDN。注意:對於全部的加鎖操做,會判斷互斥量是否被捨棄,並重置m_isMutexAbandoned
狀態。所以加鎖失敗時,你能夠調用IsMutexAbandoned
查看是不是由於互斥量被捨棄引發的。
Unlock
// Mutex.cpp void Mutex::Unlock() { ReleaseMutex(m_mutex); }
9 NoLock Class
對於鎖框架而言,
NoLock
類在單線程環境下,只是一個佔位符(個人註釋:佔位符的意思就是不包含具體的操做函數,只是爲了保持與其它環境下好比多線程,多進程的使用一致性,可參見後面的示例代碼)。
咱們的Nolock
類繼承於 BaseLock
類,以下:
// NoLock.h #include "BaseLock.h" class NoLock :public BaseLock { public: NoLock(); NoLock(const NoLock& b); virtual ~NoLock(); NoLock & operator=(const NoLock&b) { return *this; } virtual bool Lock(); virtual long TryLock(); virtual long TryLockFor(const unsigned int dwMilliSecond); virtual void Unlock(); private: };
正如上面所說,NoLock
類只是一個佔位符,因此沒有任何成員變量,也沒有鎖機制函數。
Implementation
// NoLock.cpp NoLock::NoLock() :BaseLock() { } NoLock::NoLock(const NoLock& b):BaseLock() { } NoLock::~NoLock() { } bool NoLock::Lock() { return true; } long NoLock::TryLock() { return 1; } long NoLock::TryLockFor(const unsigned int dwMilliSecond) { return 1; } void NoLock::Unlock() { }
NoLock
沒有作任何事情,只是有返回值時返回真。
10 Auto-release Lock
當進行同步操做時,匹配加鎖和解鎖是一件很是惱火的事情,因此建立一個擁有自動釋放機制的結構體是很是有必要的。咱們能夠擴展BaseLock
類並按照以下的代碼去實現:
// BaseLock.h Class BaseLock { public: ... class BaseLockObj { public: BaseLockObj(BaseLock *lock); virtual ~BaseLockObj(); BaseLockObj &operator=(const BaseLockObj & b) { return *this; } private: BaseLockObj(); BaseLockObj(const BaseLockObj & b){assert(0);m_lock=NULL;} /// The pointer to the lock used. BaseLock *m_lock; }; ... }; /// type definition for lock object typedef BaseLock::BaseLockObj LockObj;
重載拷貝操做函數,並設爲私有,是爲了防止從其它對象進行拷貝。缺省的構造函數被設爲私有,因此必須經過傳遞指向BaseLock
對象的指針來調用構造函數。
全局聲明 BaseLock::BaseLockObj爲 LockObj,便於訪問。
// BaseLock.cpp BaseLock::BaseLockObj::BaseLockObj(BaseLock *lock) { assert(lock); m_lock=lock; if(m_lock) m_lock->Lock(); } BaseLock::BaseLockObj::~BaseLockObj() { if(m_lock) { m_lock->Unlock(); } } BaseLock::BaseLockObj::BaseLockObj() { m_lock=NULL; }
如代碼所示,當BaseLockObj
被建立時,調用構造函數並執行Lock
,自動加鎖。
同理,析構時,解鎖被調用執行。
這些實現對於全部的同步基元都適用,包括 NoLock
,所以它們擁有了統一的接口,而且都繼承自BaseLock
類。
示例:
... BaseLock *someLock = new CriticalSectionEx(); // declared in somewhere accessible ... void SomeThreadFunc() { ... if (test==0) { LockObj lock(someLock); // Lock is obtained automatically ... } /// When leaving the if statement lock object is destroyed, so the Lock is automatically released ... } ...
11 Use-case Examples
聲明同步對象
... // MUTEX BaseLock * pLock = new Mutex (); // OR Mutex cLock; ... // SEMAPHORE BaseLock * pLock = new Semaphore(); // OR Semaphore cLock; ... // CRITICAL SECTION BaseLock * pLock = new CriticalSectionEx(); // OR CriticalSectionEx cLock; ... // NOLOCK BaseLock *pLock = new NoLock(); // OR NoLock cLock; ...
同一應用程序,對於不一樣的環境好比單線程,多線程,多進程均可以這樣聲明處理過程:
... BaseLock *pLock=NULL; #if SINGLE_THREADED pLock = new NoLock(); #elif MULTI_THREADED pLock = new CriticalSectionEx(); #else // MULTI_PROCESS pLock = new Mutex(); #endif ...
... BaseLock * pLock = new Semaphore(2); // OR Semaphore cLock(2); ...
... BaseLock * pLock = new Mutex (_T("MutexName")); //OR Mutex cLock(_T("MutexName")); ... // For semaphore, lock count must be input, in order to give semaphore name BaseLock * pLock = new Semaphore(1, _T("SemaphoreName")); //OR Semaphore cLock(1, _T("SemaphoreName")); ...
對於信號量,此時應該給定初始計數值,而後設定名稱。
12 示例1 (
手動加鎖)
void SomeFunc(SomeClass *sClass) { ... pLock->Lock(); //OR cLock.Lock(); ... pLock->Unlock(); //OR cLock.Unlock(); }
13 示例2 (
自動加鎖)
void SomeFunc() { LockObj lock(pLock); //OR LockObj lock(&cLock); ... } // Lock is auto-released when exiting the function.
或者這樣:
void SomeFunc(bool doSomething) { ... if(doSomething) { LockObj lock(pLock); //OR LockObj lock(&cLock); ... }// Lock is auto-released when exiting the if-statement. ... }
14 示例3(TryLock/TryLockFor)
... if(pLock->TryLock()) //OR cLock.TryLock() { // Lock obtained ... pLock->Unlock(); //OR cLock.Unlock(); } ...
或者:
... if(pLock->TryLockFor(100)) //OR cLock.TryLockFor(100) // TryLock for 100 millisecond { // Lock obtained ... pLock->Unlock(); //OR cLock.Unlock(); }
更多的例子能夠查看 EpServerEngine 的源代碼。更多的細節能夠看這篇文章:
("EpServerEngine - A lightweight Template Server-Client Framework using C++ and Windows Winsock" )
Synchronization in Multithreaded Applications with MFC by Arman S.
EpServerEngine - A lightweight Template Server-Client Framework using C++ and Windows Winsock by Woong Gyu La
License
This article, along with any associated source code and files, is licensed under The MIT License
********************************************************************************
原文:http://www.codeproject.com/Articles/421976/How-to-create-a-Simple-Lock-Framework-for-Cplusplu
源代碼:http://files.cnblogs.com/wb-DarkHorse/SimpleLockFramework.zip