如何写优雅的代码(5)——远离临界区噩梦
//========================================================================
//TITLE:
// 如何寫優(yōu)雅的代碼(5)--遠離臨界區(qū)噩夢
//AUTHOR:
// norains
//DATE:
// Tuesday 01- December-2009
//Environment:
// WINDOWS CE 5.0
//========================================================================
在平時的多線程編程中,我們往往離不開臨界區(qū)。會用,甚至是用得好的朋友,估計不少;但用得萬無一失的,相對而言,可能就會少很多。
不信?我們來看看下面這段代碼:
try { EnterCriticalSection(&g_cs); Cal3rdPartCode(pParam); LeaveCriticalSection(&g_cs); } catch(...) { //There is error //dwRet = 0; }
這段代碼看起來似乎沒有什么問題?但如果說,這段代碼會引發(fā)孤立臨界區(qū)的問題,你會信么?換句話說,如果Cal3rdPartCode(pParam)函數會拋出異常,那么代碼就直接跳轉到catch部分。那事情就嚴重了,因為代碼根本就沒有調用LeaveCriticalSection(&g_cs),將g_cs這臨界區(qū)完全孤立了起來,別的線程就只能一直等待該臨界區(qū)。
似乎情況很糟糕,不是么?當然,糟糕并不代表陷入世界末日,雖然路途上充滿了荊棘,但畢竟我們還是能看到陽光。
我們總不能在一棵樹上吊死吧。換個角度,臨界區(qū)的使用,必須是EnterCriticalSection和LeaveCriticalSection配套使用。那么我們想想,C++里面,有什么東西是強制配套的,根本不用使用者干預的?也許有的朋友已經想到了。沒錯,是類,類的構造函數和析構函數。
正常使用的話,無論使用者愿不愿意,類的構造函數和析構函數都是被編譯器強制配套使用。所以,我們可以在構造函數中調用EnterCriticalSection,在析構函數則是LeaveCriticalSection。
根據此思想,我們最簡單的用類來調用臨界區(qū)的代碼誕生了:
class CLock { public: CLock(LPCRITICAL_SECTION pCriticalSection): m_ pCriticalSection(pCriticalSection) { EnterCriticalSection(m_ pCriticalSection); }; virtual ~CLock() { LeaveCriticalSection (m_ pCriticalSection); }; private: LPCRITICAL_SECTION m_ pCriticalSection; };
使用上也是簡單明了:
void CallFunctionProc() { CLock lock(&g_cs); }
但這樣完美了么?很遺憾,這只是萬里長征的第一步,我們的路還很漫長。最根本的問題,我們如何確保傳入的臨界區(qū)指針是有效的?換句話說,我們無法保證傳入的臨界區(qū)指針是沒有調用過DeleteCriticalSection的。
如果代碼是如下的書寫,很明顯,肯定會有我們無法預料的異常:
void CallFunctionProc() { DeleteCriticalSection(&g_cs); ... CLock lock(&g_cs); }
那有沒有這么一種機制,我們可以通過它進入臨界區(qū),但我們卻不能讓它刪除臨界區(qū),或是即使刪除了,我們也有足夠的信息獲知。
萬事無絕對,既然我們能想到,那么我們就能做到。人有多大膽,地有多大產,在這個奇跡倍出的IT界,有時候也并不只是一句華而不實的口號。
首先,我們將創(chuàng)建臨界區(qū)的操作封裝于類中,使用者只能獲得創(chuàng)建的序號;然后,如果想進入臨界區(qū),只要傳入序號即可;最后,如果不需要該臨界區(qū),那么傳入序號刪除即可。這樣的好處是,通過序號這個橋梁,既能使用臨界區(qū)的功能,又不會引發(fā)臨界區(qū)的不穩(wěn)定。
我們先來看看創(chuàng)建的函數:
static DWORD CLock::Create() { CRITICAL_SECTION* pcsCriticalSection = new CRITICAL_SECTION(); if(pcsCriticalSection == NULL) { return Lock::INVALID_INDEX; } __try { InitializeCriticalSection(pcsCriticalSection); } __except(GetExceptionCode() == STATUS_NO_MEMORY) { //Failed to intialize the critical section, so delete the object created by new operate. delete pcsCriticalSection; return Lock::INVALID_INDEX; } return AddTable(pcsCriticalSection); }
雖然我們將Create函數封裝于CLock類中,但我們不打算需要通過對象才能調用,所以我們以static進行修飾,讓其成為類函數。那么,我們調用的時候,就可以直接通過類作用域使用:
DWORD dwIndex = CLock::Create();
我們留意一下這句:
CRITICAL_SECTION* pcsCriticalSection = new CRITICAL_SECTION();
這句代碼用來在棧中創(chuàng)建CRITICAL_SECTION對象。之所以這么做,是因為如果使用局部變量,那么在函數返回時,局部變量就被刪除。而通過new創(chuàng)建出來的對象,除非我們調用delete,否則在程序運行期中一直存在。這樣就能達到創(chuàng)建的目的。
接下來的異常捕獲部分可能是大家都會忽略的。根據MSDN的文檔,InitializeCriticalSection并不保證絕對能創(chuàng)建成功。當內存不足時,就會拋出STATUS_NO_MEMORY異常。所以我們必須捕獲這個異常,并進行相應的處理。
話又說回來,在我們平時直接調用臨界區(qū)的方式中,如果出現這個異常是很麻煩的事情。因為CRITICAL_SECTION對象的內部細節(jié)沒有公布,我們無法根據CRITICAL_SECTION對象來確定臨界區(qū)是否有效,也就無法保證代碼的健壯性。
如果一切順利,我們就通過AddTable將臨界區(qū)放入存儲空間中。
AddTable的實現如下:
static DWORD CLock::AddTable(CRITICAL_SECTION* pcsCriticalSection) { if(ms_pmpCriticalSection == NULL) { ms_pmpCriticalSection = new std::map<DWORD,CriticalSectionData>(); if(ms_pmpCriticalSection == NULL) { return Lock::INVALID_INDEX; } else { ms_dwIndex = 0; } } CriticalSectionData newCriticalSectionData = {pcsCriticalSection,0}; InterlockedIncrement(reinterpret_cast<LONG *>(&ms_dwIndex)); ms_pmpCriticalSection->insert(std::make_pair(ms_dwIndex,newCriticalSectionData)); return ms_dwIndex; }
ms_pmpCriticalSection是一個成員變量指針,其在頭文件聲明如下:
static std::map<DWORD,CriticalSectionData> *ms_pmpCriticalSection;
CriticalSectionData是我們定義的一個結構體,有兩個成員變量,一個是指向臨界區(qū)對象,另一個則記錄了進入該臨界區(qū)的次數:
struct CriticalSectionData { CRITICAL_SECTION *pCriticalSection; LONG lLockCount; };
看到這里,可能有的朋友說,CRITICAL_SECTION不是有個LockCount成員么,直接使用它不就行了?從理論上來說,這是可以的。但對于該結構,微軟并沒有描述文檔,換而言之,微軟保留了變更其成員的權利。雖然我們可以相信,微軟一般不會做這種費力不討好的事,但誰知道哪天微軟又心血來潮呢?所以,與其將代碼建立于微軟的信任,還不如建立于我們的掌握之中。
我們稍微回頭看看,為什么我們定義ms_pmpCriticalSection為指針,而不是直接作為普通變量?也就是說,為什么不直接這樣定義:
static std::map<DWORD,CriticalSectionData> ms_mpCriticalSection;
如果這樣定義,會有很大的風險。因為如果有一個類在構造函數調用了CLock::Create,而該類之后的對象又有一個靜態(tài)聲明,那么就會涉及到靜態(tài)變量初始化順序的不可控問題。因為在你調用CLock::Create的時候,ms_mpCriticalSection對象很有可能還有進行初始化。
例如:
class CTest { public: CText(): m_dwLock(CLock::Create()) { } private: DWORD m_dwLock; };
//這里很可能會出錯,因為其構造函數調用了CLock::Create,而ms_mpCriticalSection很可能還沒有進行初始化 static CTest test;
為了避免靜態(tài)變量初始化導致的問題,我們只能采用new來進行分配。
在這里還有一個小技巧,為了避免繁瑣的算法,我們直接采用了STL的map。這樣,在后續(xù)的查找中,我們就能比較輕松。
既然返回的只是序號,那么我們的構造函數也必然需要進行相應的修改:
CLock::CLock(DWORD dwIndex,BOOL *pRes = NULL): m_pcsCriticalSection(NULL) { //In order to make the source code simple, set the result as FALSE at first. if(pRes != NULL) { *pRes = FALSE; } m_pcsCriticalSection = GetObject(dwIndex); if(m_pcsCriticalSection != NULL) { if(pRes != NULL) { *pRes = TRUE; } InterlockedIncrement(&m_pcsCriticalSection->lLockCount); EnterCriticalSection(m_pcsCriticalSection->pCriticalSection); } }
從使用角度來說,我們只需要一個序號的形參即可。但我們想知道能不能成功進入臨界區(qū),而構造函數又不能擁有返回值,所以我們就額外多增加了一個pRes形參,用來在函數返回后指明當前進入臨界區(qū)的狀況。
GetObject是我們定義的一個函數,具體實現將在后面說明。現在只需要知道其實返回創(chuàng)建的一個臨界區(qū)數據。如果獲取一個臨界區(qū)數據對象不為NULL,那么我們在進入臨界區(qū)之前,先將lLockCount的計數增加1,標明當前進入臨界區(qū)的次數。
其實很簡單,和之前我們的最簡單的構造函數相比,只是傳入的形參不同。而恰恰只是這一點不同,就能使我們的代碼臨界區(qū)有一個質的飛躍。
再轉回頭,我們看看GetObject的實現:
CLock::CriticalSectionData* CLock::GetObject(DWORD dwIndex) { if(ms_pmpCriticalSection == NULL) { ASSERT(FALSE); return NULL; } std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->find(dwIndex); if(iter != ms_pmpCriticalSection->end()) { return &iter->second; } else { return NULL; } }
代碼并不復雜,只是從ms_pmpCriticalSection搜索出對應序號的數值,然后返回。如果該序號不存在,則返回NULL。
到這里,使用上的功能我們基本上已經完成。但似乎還缺了點什么,對,沒錯,還缺刪除函數。刪除函數并不復雜,刪除臨界區(qū)后,然后調用delete刪除創(chuàng)建的棧,最后再從ms_pmpCriticalSection中刪掉相應的序號即可。但我們需要考慮到使用者的便利性,不僅可以刪除對應序號的對象,還能簡單地刪除所有。所以,對于刪除函數,我們定義兩個。而這兩個刪除函數,都應該通過不同的手法調用這個內部刪除函數:
static BOOL CLock::Delete(const std::map<DWORD,CriticalSectionData>::iterator &iter) { if(ms_pmpCriticalSection == NULL) { ASSERT(FALSE); return FALSE; } if(iter == ms_pmpCriticalSection->end()) { return FALSE; } if(GetLockCount(iter->first) != 0) { ASSERT(FALSE); return FALSE; } if(iter->second.pCriticalSection == NULL) { return FALSE; } DeleteCriticalSection(iter->second.pCriticalSection); delete iter->second.pCriticalSection; return TRUE; }
形參是傳入一個迭代器,然后根據該迭代器做相應的操作。
那么,我們public的刪除相應序號的函數可以如下:
BOOL CLock::Delete(DWORD dwIndex) { if(ms_pmpCriticalSection == NULL) { ASSERT(FALSE); return FALSE; } std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->find(dwIndex); if(Delete(iter) != FALSE) { ms_pmpCriticalSection->erase(iter); CheckAndDeleteCriticalSection(); return TRUE; } else { return FALSE; } }
函數過程簡單明了,從通過find函數查找相應key的位置,然后傳遞給Delete函數。如果Delete返回值為TRUE,那么我們就通過erase擦除。我們之所以沒有在Delete(const std::map<DWORD,CriticalSectionData>::iterator &iter)函數中進行擦除,是因為map的迭代器,只要一刪除,就會失效,這對后續(xù)的操作有所不利。所以,我們只能根據其返回值再確定是否刪除。
CheckAndDeleteCriticalSection()函數用來判斷ms_pmpCriticalSection是否為空,如果為空,直接調用delete刪除該指針:
static void CLock::CheckAndDeleteCriticalSection() { if(ms_pmpCriticalSection->empty() != FALSE) { delete ms_pmpCriticalSection; ms_pmpCriticalSection = NULL; } }
有了這些基礎,那么我們的刪除所有的函數就簡單了:
static void CLock::DeleteAll() { if(ms_pmpCriticalSection == NULL) { ASSERT(FALSE); return; } for(std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->begin(); iter != ms_pmpCriticalSection->end(); ) { //The iterator would be destroy when you call erase,so I must call just as follows. if(Delete(iter) != FALSE) { ms_pmpCriticalSection->erase(iter ++); } else { ++ iter; } } CheckAndDeleteCriticalSection(); }
首先我們判斷ms_pmpCriticalSection是否為NULL,為NULL則什么都不做,返回。然后,再遍歷整個存儲空間,傳入其相應的迭代器進行操作。如果Delete返回不為FALSE,我們就調用erase進行刪除。
在這里一個小細節(jié)需要留意,就是這段代碼:
for(std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->begin(); iter != ms_pmpCriticalSection->end(); ) { //The iterator would be destroy when you call erase,so I must call just as follows. if(Delete(iter) != FALSE) { ms_pmpCriticalSection->erase(iter ++); } else { ++ iter; } }
對于這段代碼,我們不能這么改寫:
for(std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->begin(); iter != ms_pmpCriticalSection->end();++ iter ) { //The iterator would be destroy when you call erase,so I must call just as follows. if(Delete(iter) != FALSE) { ms_pmpCriticalSection->erase(iter); } }
因為我們知道,map容器只要進行erase操作,那么相應的迭代器就會失效。如果采用的是后面一種寫法,那么我們調用erase操作后,iter就已經無效,循環(huán)中的iter就變成一個不可預料的操作。
現在,就是完整羅列代碼的時候了:
頭文件:
#pragma once #include "Windows.h" #include <map> namespace Lock { const DWORD INVALID_INDEX = 0xFFFFFFFF; }; class CLock { public: //---------------------------------------------------------------------------------------- //Description: // Create the lock object and return the index //Return Values: // If the value is INVALID_INDEX, it means failed. Others is succeeded. //----------------------------------------------------------------------------------------- static DWORD Create(); //--------------------------------------------------------------------------------------------- //Description: // Delete all the created critical section.When you call the function,you must be sure that //there isn't other threads to enter the critical section.After succeed in calling the function, //the GetSize() returns 0, otherwise it doesn't delete all the critical section object. //--------------------------------------------------------------------------------------------- static void DeleteAll(); //--------------------------------------------------------------------------------------------- //Description: // Delete the created critical section on the index. //Parameters: // dwIndex : [in]The index of object to delete //Return Values: // FALSE means failed, maybe the index is locked. //--------------------------------------------------------------------------------------------- static BOOL Delete(DWORD dwIndex); //---------------------------------------------------------------------------------------- //Description: // Get the size of the the critical section //---------------------------------------------------------------------------------------- static DWORD GetSize(); //------------------------------------------------------------------------------------------------ //Description: // Get the lock count //Parameters: // dwIndex : [in] The index to check. //Return Values: // 0 means there is not the thread to lock the index of object or the index if invalid //Others means the count of lock //--------------------------------------------------------------------------------------------------- static LONG GetLockCount(DWORD dwIndex); //---------------------------------------------------------------------------------------- //Description: // The construct is used for enter the critical section //Parameters: // dwIndex : [in] The index to lock // pRes : [in] The result of locking. TRUE means succeeded, FALSE means failed. // If you don't want this value,you could set NULL. //---------------------------------------------------------------------------------------- CLock(DWORD dwIndex,BOOL *pRes = NULL); //---------------------------------------------------------------------------------------- //Description: // The destruct is used for leave the critical section //---------------------------------------------------------------------------------------- virtual ~CLock(); private: //---------------------------------------------------------------------------------------- //Description: // Add the critical section object to the table. //Parameters: // pcsCriticalSection : [in] The object to add. //Parameters: // Return the index in the table. //-------------------------------------------------------------------------------------- static DWORD AddTable(CRITICAL_SECTION* pcsCriticalSection); //---------------------------------------------------------------------------------------- //Description: // The struct value is for the internal value //---------------------------------------------------------------------------------------- struct CriticalSectionData { CRITICAL_SECTION *pCriticalSection; LONG lLockCount; }; //---------------------------------------------------------------------------------------- //Description: // Delete the object base on the iterator positon //Parameters: // iter : [in] The iterator to delete //----------------------------------------------------------------------------------------- static BOOL Delete(const std::map<DWORD,CriticalSectionData>::iterator &iter); //---------------------------------------------------------------------------------------- //Description: // Get the critical section object //Parameters: // dwIndex : [in] The index to get. //Return Values: // NULL means failed,others is succeeded. //---------------------------------------------------------------------------------------- static CriticalSectionData* GetObject(DWORD dwIndex); //---------------------------------------------------------------------------------------- //Description: // Check and delete the critical section which is allocate by new operate. //---------------------------------------------------------------------------------------- static void CheckAndDeleteCriticalSection(); private: static std::map<DWORD,CriticalSectionData> *ms_pmpCriticalSection; static DWORD ms_dwIndex; CriticalSectionData *m_pcsCriticalSection; };
.cpp實現文件:
#include "Lock.h" //---------------------------------------------------------------------------------- //The static member std::map<DWORD,CLock::CriticalSectionData> * CLock::ms_pmpCriticalSection = NULL; DWORD CLock::ms_dwIndex = 0; //---------------------------------------------------------------------------------- CLock::CLock(DWORD dwIndex,BOOL *pRes): m_pcsCriticalSection(NULL) { //In order to make the source code simple, set the result as FALSE at first. if(pRes != NULL) { *pRes = FALSE; } m_pcsCriticalSection = GetObject(dwIndex); if(m_pcsCriticalSection != NULL) { if(pRes != NULL) { *pRes = TRUE; } InterlockedIncrement(&m_pcsCriticalSection->lLockCount); EnterCriticalSection(m_pcsCriticalSection->pCriticalSection); } } CLock::~CLock() { if(m_pcsCriticalSection != NULL) { LeaveCriticalSection(m_pcsCriticalSection->pCriticalSection); InterlockedDecrement(&m_pcsCriticalSection->lLockCount); } } DWORD CLock::Create() { CRITICAL_SECTION* pcsCriticalSection = new CRITICAL_SECTION(); if(pcsCriticalSection == NULL) { return Lock::INVALID_INDEX; } __try { InitializeCriticalSection(pcsCriticalSection); } __except(GetExceptionCode() == STATUS_NO_MEMORY) { //Failed to intialize the critical section, so delete the object created by new operate. delete pcsCriticalSection; return Lock::INVALID_INDEX; } return AddTable(pcsCriticalSection); } void CLock::DeleteAll() { if(ms_pmpCriticalSection == NULL) { ASSERT(FALSE); return; } for(std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->begin(); iter != ms_pmpCriticalSection->end(); ) { //The iterator would be destroy when you call erase,so I must call just as follows. if(Delete(iter) != FALSE) { ms_pmpCriticalSection->erase(iter ++); } else { ++ iter; } } CheckAndDeleteCriticalSection(); } CLock::CriticalSectionData* CLock::GetObject(DWORD dwIndex) { if(ms_pmpCriticalSection == NULL) { ASSERT(FALSE); return NULL; } std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->find(dwIndex); if(iter != ms_pmpCriticalSection->end()) { return &iter->second; } else { return NULL; } } DWORD CLock::GetSize() { if(ms_pmpCriticalSection == NULL) { ASSERT(FALSE); return 0; } return ms_pmpCriticalSection->size(); } DWORD CLock::AddTable(CRITICAL_SECTION* pcsCriticalSection) { if(ms_pmpCriticalSection == NULL) { ms_pmpCriticalSection = new std::map<DWORD,CriticalSectionData>(); if(ms_pmpCriticalSection == NULL) { return Lock::INVALID_INDEX; } else { ms_dwIndex = 0; } } CriticalSectionData newCriticalSectionData = {pcsCriticalSection,0}; InterlockedIncrement(reinterpret_cast<LONG *>(&ms_dwIndex)); ms_pmpCriticalSection->insert(std::make_pair(ms_dwIndex,newCriticalSectionData)); return ms_dwIndex; } BOOL CLock::Delete(DWORD dwIndex) { if(ms_pmpCriticalSection == NULL) { ASSERT(FALSE); return FALSE; } std::map<DWORD,CriticalSectionData>::iterator iter = ms_pmpCriticalSection->find(dwIndex); if(Delete(iter) != FALSE) { ms_pmpCriticalSection->erase(iter); CheckAndDeleteCriticalSection(); return TRUE; } else { return FALSE; } } BOOL CLock::Delete(const std::map<DWORD,CriticalSectionData>::iterator &iter) { if(ms_pmpCriticalSection == NULL) { ASSERT(FALSE); return FALSE; } if(iter == ms_pmpCriticalSection->end()) { return FALSE; } if(GetLockCount(iter->first) != 0) { ASSERT(FALSE); return FALSE; } if(iter->second.pCriticalSection == NULL) { return FALSE; } DeleteCriticalSection(iter->second.pCriticalSection); delete iter->second.pCriticalSection; return TRUE; } LONG CLock::GetLockCount(DWORD dwIndex) { CriticalSectionData *pCriticalSection = GetObject(dwIndex); if(pCriticalSection == NULL) { return 0; } else { return pCriticalSection->lLockCount; } } void CLock::CheckAndDeleteCriticalSection() { if(ms_pmpCriticalSection->empty() != FALSE) { delete ms_pmpCriticalSection; ms_pmpCriticalSection = NULL; } }
簡單地調用示范如下:
//獲取相應的序號 const DWORD dwIndex = CLock::Create(); //進入臨界區(qū) CLock lock(dwIndex); //刪除相應的臨界區(qū) CLock::Delete(dwIndex);
總結
以上是生活随笔為你收集整理的如何写优雅的代码(5)——远离临界区噩梦的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于教育类app开发,未来该如何发展?
- 下一篇: 如何提高程序员的生产率 (2)