Win32下两种用于C++的线程同步类(多线程实现加锁解锁)

使用Win32提供的临界区可以方便的实现线程锁:

// 全局:
CRITICAL_SECTION cs;
InitializeCriticalSection( & cs);
// 线程1:
EnterCriticalSection( & cs);
int a = s.a;
int b = s.b;
LeaveCriticalSection( & cs);
// 线程2:
EnterCriticalSection( & cs);
s.a ++ ;
s.b – ;
LeaveCriticalSection( & cs);
// 最后:
DeleteCriticalSection( & cs);

  代码中的临界区变量(cs)就可以看作是变量s的锁,当函数EnterCriticalSection返回时,当前线程就获得了这把锁,之后就是对变量的访问了。访问完成后,调用LeaveCriticalSection表示释放这把锁,允许其他线程继续使用它。

  如果每当需要对一个变量进行加锁时都需要做这些操作,显得有些麻烦,而且变量cs与s只有逻辑上的锁关系,在语法上没有什么联系,这对于锁的管理带来了不小的麻烦。程序员总是最懒的,可以想出各种偷懒的办法来解决问题,例如让被锁的变量与加锁的变量形成物理上的联系,使得锁变量成为被锁变量不可分割的一部分,这听起来是个好主意。

  首先想到的是把锁封闭在一个类里,让类的构造函数和析构函数来管理对锁的初始化和锁毁动作,我们称这个锁为“实例锁”:

class InstanceLockBase
… {
CRITICAL_SECTION cs;
protected :
InstanceLockBase() … { InitialCriticalSection( & cs); }
~ InstanceLockBase() … { DeleteCriticalSection( & cs); }
} ;

  如果熟悉C++,看到这里一定知道后面我要干什么了,对了,就是继承,因为我把构造函数和析构函数都声明为保护的(protected),这样唯一的作用就是在子类里使用它。让我们的被保护数据从这个类继承,那么它们不就不可分割了吗:

struct MyStruct: public InstanceLockBase
… { … } ;

  什么?结构体还能从类继承?当然,C++中结构体和类除了成员的默认访问控制不同外没有什么不一样,class能做的struct也能做。此外,也许你还会问,如果被锁的是个简单类型,不能继承怎么办,那么要么用一个类对这个简单类型进行封装(记得Java里有int和Integer吗),要么只好手工管理它们的联系了。如果被锁类已经有了基类呢?没关系,C++是允许多继承的,多一个基类也没什么。

  现在我们的数据里面已经包含一把锁了,之后就是要添加加锁和解锁的动作,把它们作为InstanceLockBase类的成员函数再合适不过了:

class InstanceLockBase
… {
 CRITICAL_SECTION cs;
 void Lock() … { EnterCriticalSection( & cs); }
 void Unlock() … { LeaveCriticalSection( & cs); }
 …
} ;

  看到这里可能会发现,我把Lock和Unlock函数都声明为私有了,那么如何访问这两个函数呢?是的,我们总是需要有一个地方来调用这两个函数以实现加锁和解锁的,而且它们总应该成对出现,但C++语法本身没能限制我们必须成对的调用两个函数,如果加完锁忘了解,那后果是严重的。这里有一个例外,就是C++对于构造函数和析构函数的调用是自动成对的,对了,那就把对Lock和Unlock的调用专门写在一个类的构造函数和析构函数中:

class InstanceLock
… {
 InstanceLockBase * _pObj;
 public :
  InstanceLock(InstanceLockBase * pObj)
  … {
   _pObj = pObj; // 这里会保存一份指向s的指针,用于解锁
   if (NULL != _pObj)
   _pObj -> Lock(); // 这里加锁
  }
  ~ InstanceLock()
  … {
   if (NULL != _pObj)
   _pObj -> Unlock(); // 这里解锁
 }
} ;

  最后别忘了在类InstanceLockBase中把InstanceLock声明为友元,使得它能正确访问Lock和Unlock这两个私有函数:

class InstanceLockBase
… {
 friend class InstanceLock;
 …
} ;

  好了,有了上面的基础,现在对变量s的加解锁管理变成了对InstanceLock的实例的生命周期的管理了。假如我们有一个函数ModifyS中要对s进行修改,那么只要在函数一开始就声明一个InstaceLock的实例,这样整个函数就自动对s加锁,一旦进入这个函数,其他线程就都不能获得s的锁了:

void ModifyS()
… {
 InstanceLock lock ( & s); // 这里已经实现加锁了
 // some operations on s
} // 一旦离开lock对象的作用域,自动解锁

  如果是要对某函数中一部分代码加锁,只要用一对大括号把它们括起来再声明一个lock就可以了:


… {
 InstanceLock lock ( & s);
 // do something …
}

  好了,就是这么简单。下面来看一个测试。

  首先准备一个输出函数,对我们理解程序有帮助。它会在输出我们想输出的内容同时打出行号和时间:

void Say( char * text)
… {
 static int count = 0 ;
 SYSTEMTIME st;
 ::GetLocalTime( & st);
 printf( ” d [d:d:d.d]%s ” , ++ count, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, text);
}

  当然,原则上当多线程都调用这个函数时应该对其静态局部变量count进行加锁,这里就省略了。

  我们声明一个非常简单的被锁的类型,并生成一个实例:

class MyClass: public InstanceLockBase
… {} ;
MyClass mc;

  子线程的任务就是对这个对象加锁,然后输出一些信息:

DWORD CALLBACK ThreadProc(LPVOID param)
… {
 InstanceLock il( & mc);
 Say( ” in sub thread, lock ” );
 Sleep( 2000 );
 Say( ” in sub thread, unlock ” );
 return 0 ;
}

  这里会输出两条信息,一是在刚刚获得锁的时间,二是在释放锁的时候,中间通过Sleep来延迟2秒。

  主线程负责开启子线程,然后也对mc加锁:

CreateThread( 0 , 0 , ThreadProc, 0 , 0 , 0 );
… {
 InstanceLock il( & mc);
 Say( ” in main thread, lock ” );
 Sleep( 3000 );
 Say( ” in main thread, lock ” );
}

  运行此程序,得到的输出如下:

001 [13:43:23.781]in main thread, lock
002 [13:43:26.781]in main thread, lock
003 [13:43:26.781]in sub thread, lock
004 [13:43:28.781]in sub thread, unlock

  从其输出的行号和时间可以清楚的看到两个线程间的互斥:当主线程恰好首先获得锁时,它会延迟3秒,然后释放锁,之后子线程才得以继续进行。这个例子也证明我们的类工作的很好。

  总结一下,要使用InstanceLock系列类,要做的就是:

  1、让被锁类从InstanceLockBase继承

  2、所有要访问被锁对象的代码前面声明InstanceLock的实例,并传入被锁对象的指针。

  附:完整源代码:

pragma once

include < windows.h >

class InstanceLock;

class InstanceLockBase
… {
 friend class InstanceLock;

 CRITICAL_SECTION cs;

 void Lock()
 … {
  ::EnterCriticalSection( & cs);
 }

 void Unlock()
 … {
  ::LeaveCriticalSection( & cs);
 }

 protected :
 InstanceLockBase()
 … {
  ::InitializeCriticalSection( & cs);
 }

 ~ InstanceLockBase()
 … {
  ::DeleteCriticalSection( & cs);
 }
} ;

class InstanceLock
… {
 InstanceLockBase * _pObj;
 public :
  InstanceLock(InstanceLockBase * pObj)
  … {
   _pObj = pObj;
   if (NULL != _pObj)
    _pObj -> Lock();
  }

 ~ InstanceLock()
 … {
  if (NULL != _pObj)
   _pObj -> Unlock();
 }
} ;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值