C++析构函数妙用-- 不会忘记的Unlock

在C++中,类的析构函数被解释为用于销毁 对象的代码块,在对象将被从内存中清除之前调用。而事实上,利用析构函数的调用时机,可以做很多普通的过程控制代码很难做到的 事情。

比如在多线程程序中的锁。在加锁和开锁的过程中,必须非常小心地配对,稍有不慎就 会少了开锁次数,使资源锁得不到打开。在有复杂控制的程序体中,这种维护是很烦琐的。

以Windows程序为例,在类 CWithLock中使用临界区定义一个锁,成员函数fun将被多个线程调 用。

  CWithLock

    {

        public:

           CWithLock();

           ~CWithLock();

 

           CRITICAL_SECTION  m_csAccessLock; // 作为锁的临界区

              

           void fun();     // 将被多线程调用的成员函数   

};

简单的情况下,在fun函数的定义中我们可 以简单地使用锁,如下:

void fun()

{

       EnterCriticalSection(&m_csAccessLock);

    // 函数体

    // 。。 。。

    LeaveCriticalSection(&m_csAccessLock);

}

然而,当函数体控制很复杂的时候,我们不得不在每个return语句前加上开锁语句,如下:

void fun()

{

       EnterCriticalSection(&m_csAccessLock);

    // 函数体

    if(..) 

{

       // 。。。。

LeaveCriticalSection (&m_csAccessLock);

return;

}else

{

    try

{

       if (…)

{

// 。。。。 

LeaveCriticalSection(&m_csAccessLock);

return;

}

}catch(…)

{

           // 。。。。 

LeaveCriticalSection (&m_csAccessLock);

return;

}

}

    // 。。 。。

    LeaveCriticalSection(&m_csAccessLock);

}

可以看出,随着程序控制的复杂化,不得不在多个地方增加开锁代码。这种多处都不得 不执行的相同操作增加了代码失误的可能性,以日后程序的维护和修改过程中,也会造成很多麻烦。如果在fun的执行过程中要使用多个共享资源,情况将更加糟糕。当代码失误存在于一个很少执行到的代码分支里 时,无疑是给程序埋下了一个定时炸弹。

加锁和开锁的函数调用代码看起来也很像,在我的程序生涯中还遇到喜欢“复制 -粘贴”的同事把这两个函数写错的事情。发现这个错误所用的代价可是很大的。 

有没有办法可以放松一下代码人员紧张的神经呢?答案是有,而且不止一种。 

第一种,利用被受垢病的goto语句。把所有的开锁 工作放到fun函数的尾部,把所有的return语句换成 goto语句,跳到开锁的位置。如下:

void fun()

{

       EnterCriticalSection(&m_csAccessLock);

    // 函数体

    if(..) 

{

       // 。。。。

goto end;

}else

{

    try

{

       if (…)

{

// 。。。。 

goto end;

}

}catch(…)

{

           // 。。。。 

goto end;

}

}

    // 。。 。。

    end:

    LeaveCriticalSection(&m_csAccessLock);

}

看起来好些了,但实际操作过程中,goto的使用给 代码也会带来很多麻烦。如goto不能跳过变量的声明;无法在函数休内动态地对某一资源开锁,以减少上 锁时间;所有的变量都声明的第一个goto之前,代码看上去也很丑,至少可读性会受到响。 

第二种方法,使用try-catch异常处理机制中的 __finally代码块。把函数体作为try代码块的一部分,在这里可以随意调用 return返回。把开锁语句放进__finally代码块中,可以保证最后总 会调用到开锁语句。

这种方法同样问题多多,不只是难看,而且也同样不够灵活。

 

最好的方法当然是第三种,也就是利用析构函数来解锁。在详述这种方法前,我们先看 看函数体或程序块的执行过程。

一个程序块就是大括号“{}”内的整个代码块。一 个函数体也是一个程序块。程序块内声明的变量,在声明时执行该变量的构造函数;当控制离开程序块时,无论执行到 “}”,还是使用break或return语句 跳出,每个在程序块里声明的变量都将被析构,他们的析构函数都将被调用。

利用这个特点,如果我们使用一个对象的实例来加锁,并在这个对象的析构函数中解锁 ,那么我们将无需显式地声明解锁过程,并总能保证加锁状态只在程序块里有效。

我们设计一个简单的类来实现这个功能:

  CLock

    {

        public:

            CLock(CRITICAL_SECTION *p_door) // 在构造函数中加锁

{

if (NULL != (m_pDoor = p_door))

    EnterCriticalSection(m_pDoor);


           ~CLock()                    // 在析构函数中开锁

{

if (NULL != m_pDoor)) 

    LeaveCriticalSection (m_pDoor);


privated:

      CRITICAL_SECTION * m_pDoor;

};

使用CLock类之后,我们将fun的函数体改写一下: 

void fun()

{

       CLock Lock(&m_csAccessLock); // 使用CLock的构造函数加锁 

    // 函数体

    if(..) 

{

       // 。。。。

return; // 无需再开锁 

}else

{

    try

{

       if (…)

{

// 。。。。 

return; // 无需再开锁

}

}catch(…)

{

           // 。。。。 

return; // 无需再开锁 

}

}

    // 。。 。。// 无需再开锁

}

我们可以看到,除了程序的第一行以外,其余地方均和普通的单线程程序没有什么不 同。程序员不用在担心开锁的问题,可以说有了一个永远不会忘记的Unlock。 

不仅如此,我们还可以使用“{}”来界定 锁的有效范围,可以在任何地方随时锁定其它的资源,并在使用结束的时候,自动开锁。如果要同时在一个代码块里锁定多个资源,析 构函数还能保证开锁的顺序,在一定程度上避免死锁。

上述CLock代码只是一个例子,并不是最好的设计 ,并且也不能满足所有的算法要求。在有些特殊的算法中,就要求资源在一个程序块中加锁,而在另一个程序块中解锁。另外还要增加 TryLock功能,使线程在访问被锁的资源后,仍能运行。

在考虑了所有的需求之后,可以设计出更好的类来实现锁。下面给出一个笔者使用的 类,仅供参考。

//******** CWithLock.h ***********************

被共享实例的类

    class CLock;

    class  CWithLock

    {

    public:

       CWithLock ();

       ~CWithLock ();

       HANDLE GetOwnerThread();

 

    protected:

       CRITICAL_SECTION  m_csAccess;

 

       inline void Lock();

       inline void UnLock();

       inline BOOL TryLock();

 

       friend class CLock;

    };

 

//******** CWithLock.cpp ***********************

CWithLock::CWithLock()

{

    InitializeCriticalSection( &m_csAccess );

}

 

CWithLock::~CWithLock()

{

    DeleteCriticalSection( &m_csAccess );

}

 

void CWithLock::Lock() 

{

    EnterCriticalSection (&m_csAccess);

}

 

    LeaveCriticalSection (&m_csAccess);

}

 

BOOL CWithLock::TryLock()

{

    return TryEnterCriticalSection(&m_csAccess);

}

 

HANDLE CWithLock::GetOwnerThread()

{

    return m_csAccess.OwningThread; 

}

//******** CLock.h ***********************

    class CWithLock;

    // 用来锁 CWithLock实例的类

    class  CLock

    {

    public:

       CLock( CWithLock &door, bool bLook = true);

       ~CLock ();

 

       void Lock();

       void UnLock();

       BOOL TryLock ();

 

       static void Lock(CWithLock &door);

       static void UnLock(CWithLock &door);

    private:

       bool m_bLocked;

       CWithLock * m_pDoor;

    };

 

//******** CLock.cpp ***********************

CLock::CLock( CWithLock &door, bool b_lock) 

{

    m_pDoor = &door;

    m_bLocked = b_lock;

    if (m_bLocked)

       Lock (*m_pDoor);

}

 

CLock::~CLock()

{

    if (m_bLocked)

       UnLock (*m_pDoor);

}

 

void CLock::Lock() 

{

    if(!m_bLocked)

    {

       m_bLocked = true;

       Lock (*m_pDoor);

    }

}

 

    if (m_bLocked)

    {

       m_bLocked = false;

       UnLock (*m_pDoor);

    }

}

 

BOOL CLock::TryLock()

{

    if(!m_bLocked)

    {

       if ( m_pDoor->TryLock() )

       {

           m_bLocked = true;

           return TRUE;

       }      

       return FALSE;

    }

    return TRUE;  

}

 

    door.Lock();

}

 

    door.UnLock();

}
posted on 2010-06-01 22:17  carekee  阅读(1737)  评论(0编辑  收藏  举报