临界区CriticalSection 的安全使用

   临界区 是应用程序互斥多线程的常用锁。 通常分4个步骤: 初始化( InitializeCriticalSection),进入临界区(EnterCriticalSection),离开(LeaveCritclSection).,释放(DeleteCriticalSection).


    隐患:

         1.  InitializeCriticalSection 可能会因为内存不足而失败。

         2. 由于可能会延迟初始化 ,系统可能会在程序调用EnterCritcalSection 时,进行初始化,所以EnterCriticalSection 也有可能会调用失败。

         3. 如果一个线程调用了EnterCritiaclSection ,另外一个线程误调了LeaveCriticalSection ,可能会是个严重的误会。

         4. 如果一个线程deleteCriticalSection ,而其他线程刚好又调用EnterCritcalSection,也会出现不测。

应对:

       对于隐患1,2, 使用 InitializeCriticalSectionAndSpinCount 来初始化, 通过返回值判断是否成功分配内存。这样EnterCirticalSection不会有失败的情况。

      对应隐患3,4, 主要是由于离散的误操作导致。为避免这种情况,可以对这些操作进行包装: 

      class TCritical{
    LPCRITICAL_SECTION m_lc;
public:

    static LPCRITICAL_SECTION CreateCritical()
    {
        LPCRITICAL_SECTION plc= new CRITICAL_SECTION;
        if(FALSE == InitializeCriticalSectionAndSpinCount(plc,0x80000000))
            return NULL;
        else
        {
            return plc ;
        }
    }

    static bool DeleteCritical(LPCRITICAL_SECTION & plc)
    {
        if(plc)
        {
            if(plc->LockCount==-1) //没有人使用了
            {
                DeleteCriticalSection(plc);
            }
            else       //如果有人使用中,不可以删除,返回个错误,
                return false ;

            delete plc ;
        }
        return true;
    }
    
    TCritical(LPCRITICAL_SECTION lc)
    {
 
        m_lc = lc ;
    }
    virtual ~ TCritical()
    {
        if(m_lc)
        {

            DWORD threadId = (DWORD)m_lc->OwningThread;

            if(threadId>0) //忘记调用LeaveCriticalSection
            {
                LeaveCriticalSection(m_lc);
            }
            
        }
    }

    //加锁
    bool   Lock()
    {
        if(m_lc ==NULL)
            return false ;

        if( m_lc->LockCount== 0)// 已经被Delete了  
            return false;
        
         EnterCriticalSection(m_lc);  

         return true;
    }

    //解锁
    bool UnLock()
    {
        if( m_lc==NULL)
            return false ;

        DWORD threadId = (DWORD)m_lc->OwningThread;
        if( threadId == ::GetCurrentThreadId())  // 必须同样线程才可以解锁
        {
            LeaveCriticalSection(m_lc);
            return true ;
        }
        return false ;
    }

};

TCritical 类可以保证我们使用临界区基本是安全的了,并且也足够灵活,如果lock 和unLock 不在同一个函数里它也可以工作。如果想咬在同一个函数里工作自动加锁解锁的话,我们需要新增一个类:

class AutoCritical
{
        
    bool m_bResult ;
    TCritical* m_ptc;
public:
    AutoCritical(TCritical *ptc)
        :m_bResult(false)
    {
        m_ptc = ptc ;

        if(m_ptc)
        {
            m_bResult = m_ptc->Lock();
        }
    }
    // 我们需要时, 可以获取到调用的结果
    bool GetResult(){ return m_bResult;}

    ~AutoCritical()
    {
        if(m_ptc)
            m_bResult = m_ptc->UnLock();
    }
};


AutoCritical 在栈上声明的对象可以保证在其生命周期结束时自动释放锁。很多时候我们希望可以在函数中尽快释放,所以需要控制其周期:

#define Begin_AutoCritical(a,cs) {AutoCritical a(cs);

#define End_AutoCritical };

使用如下:   

    LPCRITICAL_SECTION plc = TCritical::CreateCritical();

    TCritical*  pa = new TCritical(plc);
      
     Begin_AutoCritical(autolock,pa);
     ....

     End_AutoCritical;  // 这里autolock 就被释放了



        



 

你可能感兴趣的:(多线程,c++)