定界加锁模式

     在多线程的程序中,当一个资源被多个线程共同访问时,就需要对该资源的访问进行加锁。加锁的操作不同的操作系统实现方式可能不同,如windows操作系统用CRITICAL_SECTION,Linux系统用pthread_mutex_t。尽管如此,对锁的操作却大致相同,一般有以下几个操作:初始化锁、加锁、解锁、删除锁。初始化锁和删除锁就像分配和释放内存一样,第一次使用前和最后一次使用后分别做一次,加锁和解锁则在每次开始访问共享资源和结束访问共享资源时各做一次。下面以Windows系统为例做介绍。

锁的变量类型: CRITICAL_SECTION hLock;
初始化锁:InitializeCriticalSection( &hLock );
删除锁:DeleteCriticalSection( &hLock );
加锁:EnterCriticalSection( &hLock );
解锁:LeaveCriticalSection( &hLock );

    其中,加锁、解锁、删除锁操作需在初始化锁成功后才能执行,否则会出现运行时错误。加锁和解锁需一一对应。

下面看一下具体的例子:

int  _tmain( int  argc, _TCHAR *  argv[])
{//主函数
CMyThread a;

//创建两个线程
a.create( 2 );

//等待线程执行完
a.wait( -1 );

//输出资源的值
cout << a.GetNum();

return 0;
}


// CMyThread类的定义
class  CMyThread :  public  CThread
{
public:
CMyThread() : m_num(
0)
{
   
//初始化锁
   InitializeCriticalSection( &hLock );
}

virtual ~CMyThread()
{
   
//删除锁
   DeleteCriticalSection( &hLock );
}


int GetNum()
{
   
return m_num;
}

protected:
unsigned thread_proc()
{//线程函数

   
forint i=0; i<1000; i++ )
   
{
    
//加锁
    EnterCriticalSection( &hLock );

    
//模拟取资源
    int a = m_num;

    
//模拟对取得的资源进行操作
    a++;
    Sleep(
0);
   
    
//模拟保存操作完的资源
    m_num = a;

    
//解锁
    LeaveCriticalSection( &hLock );
   }

   
return 0;
}

private:
int m_num; //共享资源
CRITICAL_SECTION hLock; //访问资源的锁
}
;

 在线程函数thread_proc内对共享资源m_num进行访问时,需分别加锁解锁,写起来比较繁琐。除此外,还有个问题,如果在开头加了锁而结尾忘了解锁的话,编译的时候不会出错,运行的时候也不会有什么错误提示,但是,当一个线程加了锁而没解锁的话,其它线程都无法再进入这个代码段,这将造成程序没反应。为了避免这些问题,我们可以用C++构造和析构自动被调用的特性,对锁进行封装,使加锁解锁操作能自动进行。封装如下:

// -----------------------mutex.h文件---------------------------
#ifndef _MUTEX_H
#define  _MUTEX_H
struct  ThreadMutex
{
ThreadMutex()
{//初始化锁
   InitializeCriticalSection( &hLock );
}


~ThreadMutex()
{//删除锁
   DeleteCriticalSection( &hLock );
}


inline 
void Lock()
{//加锁
   EnterCriticalSection( &hLock );
}


inline 
void Unlock()
{//解锁
   LeaveCriticalSection( &hLock );
}


CRITICAL_SECTION hLock;
}
;

// do nothing when lock
struct  NullMutex
{
inline 
void Lock()
{
}

inline 
void Unlock()
{
}

}
;

template
< class  T >
class  CAutoGuard
{
public:
CAutoGuard(T 
&mtx) : m_mtx( mtx )
{
   m_mtx.Lock();
}


~CAutoGuard(void)
{
   m_mtx.Unlock();
}


protected:
&m_mtx;
}
;

#ifndef THREAD_MUTEX
#define  THREAD_MUTEX ThreadMutex
#endif

#ifndef NULL_MUTEX
#define  NULL_MUTEX NullMutex
#endif

// 自动锁宏定义
// tmp_var:CAutoGuard 变量名称,局部变量
// MUTEX_TYPE:mutex类型,NULL_MUTEX或THREAD_MUTEX
// mtx:MUTEX_TYPE的锁对象
#define  AUTO_GUARD( guard_tmp_var, MUTEX_TYPE, mtx ) 
CAutoGuard
< MUTEX_TYPE >  guard_tmp_var(mtx)

#endif
// -----------------------mutex.h文件结束---------------------------

ThreadMutex构造和析构进行初始化锁和删除锁操作,CAutoGuard的构造和析构进行加锁和解锁操作。两个配合起来用的话就是这样的:
ThreadMutex m_lock; //定义锁变量,m_lock的构造和析构进行了初始化锁和删除锁操作
CAutoGuard<ThreadMutex> g( m_lock ); //定界加锁,g生命周期结束时自动解锁。

改进使用定界加锁后的CMyThread线程定义如下:

 

class  CMyThread :  public  CThread
{
public:
CMyThread() : m_num(
0)
{
}

virtual ~CMyThread()
{
}

int GetNum()
{
   
return m_num;
}

protected:
unsigned thread_proc()
{//线程函数

   
forint i=0; i<1000; i++ )
   
{
    
//定界加锁
    AUTO_GUARD( g, ThreadMutex, m_lock );

    
//模拟取资源
    int a = m_num;

    
//模拟对取得的资源进行操作
    a++;
    Sleep(
0);
   
    
//模拟保存操作完的资源
    m_num = a;
   }

   
return 0;
}

private:
int m_num; //共享资源
ThreadMutex m_lock; //访问资源的锁
}
;

加锁解锁操作只要一个语句AUTO_GUARD( g, ThreadMutex, m_lock );,我们这里用了宏定义,宏展开就是:CAutoGuard<ThreadMutex> g(m_lock);这下子就不用担心忘了解锁的问题了,且代码简洁多了。

注:CThread定义见另一篇文章:“包装模式”。

你可能感兴趣的:(定界加锁模式)