如何写优雅的代码(5)——远离临界区噩梦

//========================================================================
//TITLE:
// 如何写优雅的代码(5)--远离临界区噩梦
//AUTHOR:
// norains
//DATE:
// Tuesday 01- December-2009
//Environment:
// WINDOWS CE 5.0
//========================================================================

  在平时的多线程编程中,我们往往离不开临界区。会用,甚至是用得好的朋友,估计不少;但用得万无一失的,相对而言,可能就会少很多。
  
  不信?我们来看看下面这段代码:  
  try   {    EnterCriticalSection(&g_cs);    Cal3rdPartCode(pParam);    LeaveCriticalSection(&g_cs);   }   catch(...)   {    //There is error    //dwRet = 0;   }

  这段代码看起来似乎没有什么问题?但如果说,这段代码会引发孤立临界区的问题,你会信么?换句话说,如果Cal3rdPartCode(pParam)函数会抛出异常,那么代码就直接跳转到catch部分。那事情就严重了,因为代码根本就没有调用LeaveCriticalSection(&g_cs),将g_cs这临界区完全孤立了起来,别的线程就只能一直等待该临界区。
  
  似乎情况很糟糕,不是么?当然,糟糕并不代表陷入世界末日,虽然路途上充满了荆棘,但毕竟我们还是能看到阳光。
  
  我们总不能在一棵树上吊死吧。换个角度,临界区的使用,必须是EnterCriticalSection和LeaveCriticalSection配套使用。那么我们想想,C++里面,有什么东西是强制配套的,根本不用使用者干预的?也许有的朋友已经想到了。没错,是类,类的构造函数和析构函数。
  
  正常使用的话,无论使用者愿不愿意,类的构造函数和析构函数都是被编译器强制配套使用。所以,我们可以在构造函数中调用EnterCriticalSection,在析构函数则是LeaveCriticalSection。
  
  根据此思想,我们最简单的用类来调用临界区的代码诞生了:
   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); }   

  但这样完美了么?很遗憾,这只是万里长征的第一步,我们的路还很漫长。最根本的问题,我们如何确保传入的临界区指针是有效的?换句话说,我们无法保证传入的临界区指针是没有调用过DeleteCriticalSection的。
  
  如果代码是如下的书写,很明显,肯定会有我们无法预料的异常:
   void CallFunctionProc() { DeleteCriticalSection(&g_cs); ... CLock lock(&g_cs); }   

  那有没有这么一种机制,我们可以通过它进入临界区,但我们却不能让它删除临界区,或是即使删除了,我们也有足够的信息获知。
  
  万事无绝对,既然我们能想到,那么我们就能做到。人有多大胆,地有多大产,在这个奇迹倍出的IT界,有时候也并不只是一句华而不实的口号。
  
  首先,我们将创建临界区的操作封装于类中,使用者只能获得创建的序号;然后,如果想进入临界区,只要传入序号即可;最后,如果不需要该临界区,那么传入序号删除即可。这样的好处是,通过序号这个桥梁,既能使用临界区的功能,又不会引发临界区的不稳定。
  
  我们先来看看创建的函数:
  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();
  
  这句代码用来在栈中创建CRITICAL_SECTION对象。之所以这么做,是因为如果使用局部变量,那么在函数返回时,局部变量就被删除。而通过new创建出来的对象,除非我们调用delete,否则在程序运行期中一直存在。这样就能达到创建的目的。
  
  接下来的异常捕获部分可能是大家都会忽略的。根据MSDN的文档,InitializeCriticalSection并不保证绝对能创建成功。当内存不足时,就会抛出STATUS_NO_MEMORY异常。所以我们必须捕获这个异常,并进行相应的处理。
  
  话又说回来,在我们平时直接调用临界区的方式中,如果出现这个异常是很麻烦的事情。因为CRITICAL_SECTION对象的内部细节没有公布,我们无法根据CRITICAL_SECTION对象来确定临界区是否有效,也就无法保证代码的健壮性。
  
  如果一切顺利,我们就通过AddTable将临界区放入存储空间中。
  
  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是我们定义的一个结构体,有两个成员变量,一个是指向临界区对象,另一个则记录了进入该临界区的次数:  
struct CriticalSectionData { CRITICAL_SECTION *pCriticalSection; LONG lLockCount; };
  
  看到这里,可能有的朋友说,CRITICAL_SECTION不是有个LockCount成员么,直接使用它不就行了?从理论上来说,这是可以的。但对于该结构,微软并没有描述文档,换而言之,微软保留了变更其成员的权利。虽然我们可以相信,微软一般不会做这种费力不讨好的事,但谁知道哪天微软又心血来潮呢?所以,与其将代码建立于微软的信任,还不如建立于我们的掌握之中。
  
  我们稍微回头看看,为什么我们定义ms_pmpCriticalSection为指针,而不是直接作为普通变量?也就是说,为什么不直接这样定义:
  static std::map<DWORD,CriticalSectionData> ms_mpCriticalSection;
  
  如果这样定义,会有很大的风险。因为如果有一个类在构造函数调用了CLock::Create,而该类之后的对象又有一个静态声明,那么就会涉及到静态变量初始化顺序的不可控问题。因为在你调用CLock::Create的时候,ms_mpCriticalSection对象很有可能还有进行初始化。
  
  例如:
class CTest { public: CText(): m_dwLock(CLock::Create()) { } private: DWORD m_dwLock; };
    
//这里很可能会出错,因为其构造函数调用了CLock::Create,而ms_mpCriticalSection很可能还没有进行初始化 static CTest test;   


  为了避免静态变量初始化导致的问题,我们只能采用new来进行分配。
  
  在这里还有一个小技巧,为了避免繁琐的算法,我们直接采用了STL的map。这样,在后续的查找中,我们就能比较轻松。
  
  既然返回的只是序号,那么我们的构造函数也必然需要进行相应的修改:
  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); } }   

  从使用角度来说,我们只需要一个序号的形参即可。但我们想知道能不能成功进入临界区,而构造函数又不能拥有返回值,所以我们就额外多增加了一个pRes形参,用来在函数返回后指明当前进入临界区的状况。
  
  GetObject是我们定义的一个函数,具体实现将在后面说明。现在只需要知道其实返回创建的一个临界区数据。如果获取一个临界区数据对象不为NULL,那么我们在进入临界区之前,先将lLockCount的计数增加1,标明当前进入临界区的次数。
  
  其实很简单,和之前我们的最简单的构造函数相比,只是传入的形参不同。而恰恰只是这一点不同,就能使我们的代码临界区有一个质的飞跃。
  
  再转回头,我们看看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。
  
  到这里,使用上的功能我们基本上已经完成。但似乎还缺了点什么,对,没错,还缺删除函数。删除函数并不复杂,删除临界区后,然后调用delete删除创建的栈,最后再从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的迭代器,只要一删除,就会失效,这对后续的操作有所不利。所以,我们只能根据其返回值再确定是否删除。
  
  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进行删除。
  
  在这里一个小细节需要留意,就是这段代码:
  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就已经无效,循环中的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(); //进入临界区 CLock lock(dwIndex); //删除相应的临界区 CLock::Delete(dwIndex);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值