您的位置:首页 > 编程语言 > C语言/C++

C++多线程面向对象解决方案

2013-05-31 17:30 288 查看
相信很多人都读过《C++沉思录》这本经典著作,在我艰难地读完整本书后,留给我印象最深的只有一句话::“用类表示概念,用类解决问题”。
关 于多线程编程,如果不是特别需要,大多数开发人员都不会特意去触碰这个似乎神秘的领域。如果在某些场合能正确并灵活地运用,多线程带来的好处是不言而喻 的。然而,任何事物都有两面性,如果程序中引入多线程,那么我们需要谨慎小心地处理许多与之相关的问题,其中最突出的就是:资源竞争、死锁和无限延迟。那 么面向对象与这些有什么关系了吗?有,面向对象的基础是封装,是的,正是封装,可以很好的解决多线程环境中的主要困境。
一.多线程环境
在开始之前,有必要先重温一下多线程环境。所谓,多线程编程,指的是在一个进程中有多个线程同时运行,每一个线程都有自己的堆栈,但是这些线程共享所有的全局变量和资源。
在引入互斥量之前,多线程的主要问题是资源竞争,所谓资源竞争就是两个或两个以上的线程在同一时间访问同一资源。为了解决这个问题,我们引入了互斥量,以同步多个线程对同一资源的访问。
然 而在引入互斥量之后,新的问题又来了。因为如果互斥量的获得和释放没有得到正确处理,就会引起严重的问题。比如,某线程获得互斥量后,就可以对受保护的资 源进行访问,但是如果访问完毕后,忘记了释放互斥量,那么其它的线程永远也无法访问那个受保护的资源了。这是一种较简单的情况,还有一种复杂的,那就是线 程1已经拥有了资源A,但是它要拥有资源B后才能释放A,而线程2了恰好相反,线程2已经拥有了资源B但是它要拥有资源A后才能释放B。这样一来,线程1和线程2就永远地相互等待,这就是所谓的死锁。
死 锁导致的问题是严重的,因为它使得程序无法正常运行下去。也许引入一些规范或约束有助于减少死锁发生的几率,比如我们可以要求,所有资源的访客(客户,使 用者)都必须在真正需要资源的时刻请求互斥量,而当资源一使用完毕,就立即释放它,另外,锁定与释放一定要是成对的。如果上面的线程1和线程2都遵守这个规范,那么上述的那种死锁情况就不会发生了。
然而,规范永远只是规范,规范能被执行多少要依赖于使用者的自觉程度有多高,这个世界上总是有对规范和约束视而不见的人存在。所以,我们希望能够强制执行类似的约束,在对使用者透明的情况下。对于约束的强制实施可以通过封装做到。
 
二.多线程面向对象解决方案
首先你需要将系统API封装成基础类,这样你就可以用面向对象的武器类对付多线程环境,二是将临界资源与对其的操作封装在一个类中。这两点的核心都是将问题集中在一个地方,防止它们泛滥在程序的各个地方。

 1.  将系统API封装成基础类。
 1.  将系统API封装成基础类。
厌倦了每次涉及共享资源操作时都需要调用InitializeCriticalSection、DeleteCriticalSection、EnterCriticalSection、LeaveCriticalSection,并且它们是成对使用的,如果你调用了EnterCriticalSection,却忘了调用LeaveCriticalSection,那么锁就永远得不到释放,并且这些API的使用是很不直观的。我喜欢将它们封装成类,只需封装一次,以后就不用再查MSDN,每个API怎么写的了,参数是什么,免去后顾之忧。而且,在类的构造函数中调用InitializeCriticalSection,析构函数中调用DeleteCriticalSection,可以防止资源泄漏。面向对象的封装真是个好东西,我们没有理由拒绝它。
来看看我封装的几个与多线程环境相关的基础类。
// CriticalSection类用于解决对临界资源的保护
class CriticalSection
{
protected:
       CRITICAL_SECTION critical_section ;
public:
       CriticalSection()
       {
              InitializeCriticalSection(&this->critical_section) ;
       }
       virtual ~CriticalSection()
       {
              DeleteCriticalSection(&this->critical_section) ;
       }
       void Lock()
       {
              EnterCriticalSection(&this->critical_section) ;
       }
       void Unlock()
       {
              LeaveCriticalSection(&this->critical_section) ;
       }
}; 
//Monitor用于解决线程之间的同步依赖
class Monitor
{
private:
HANDLE event_obj ;
public:
Monitor(BOOL isManual = FALSE)
{
        this->event_obj = CreateEvent(NULL ,FALSE ,isManual ,"NONAME") ;
}
~Monitor()
{
        //ReleaseEvent()
        CloseHandle(this->event_obj) ;

void SetIt()
{
        // 如果为auto,则SetEvent将event obj设为有信号,当一个等待线程release后,
        //event obj自动设为无信号
        //如果是manual,则release所有等待线程,且没有后面自动重设
        SetEvent(this->event_obj) ;
}
void ResetIt()
{    
        //手动将event obj设为无信号
        ResetEvent(this->event_obj) ;

void PulseI
4000
t()
{
        // 如果为auto,则PulseEvent将event obj设为有信号,当一个等待线程release后,
        //event obj自动设为无信号
        //如果是manual,PulseEvent将event obj设为有信号,且release所有等待线程,
        //然后将event obj自动设为无信号
        PulseEvent(this->event_obj) ;

DWORD Wait(long timeout)
{
        return WaitForSingleObject(this->event_obj ,timeout) ;
}
}; 
//Thread是对线程的简单封装
class Thread
{
private:    
HANDLE threadHandle ; 
unsigned long  threadId ;    
    unsigned long  exitCode ;
BOOL needTerminate ;

 public:
 public:
Thread(unsigned long exit_code = 0 )
{           
        this->exitCode = exit_code ;
        this->needTerminate = FALSE ;
}

 ~Thread(void)
 ~Thread(void)
{
        if(this->needTerminate)
        {           
               TerminateThread(this->threadHandle ,this->exitCode) ;
        }

long GetTheThreadID()
{
        return this->threadId ;

void Start(FunPtr pfn ,void* pPara)//启动线程
{
        this->threadHandle = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)(pfn) ,pPara ,0,&(this->threadId));  
}

void SetTerminateSymbol(BOOL need_Terminate)
{
        this->needTerminate = need_Terminate ;

void wait(void)
{
        WaitForSingleObject(this->threadHandle,INFINITE) ; //用于阻塞宿主线程,使其不能早于本线程结束
}
}; 
        在大多数的多线程环境中,上述的几个类已经够用了,不如要实现更强劲的同步机制,你可以仿照上面自己进行封装。

2.  将临界资源与对其的操作封装在一个类中,如果这样做,锁的操作自动在类的实现中完成,而外部使用者不用关心是否处在多线程环境。也就是说这个类是线程安全的,在单线程和多线程环境下都可以使用。
比如我们经常需要使用线程安全的容器,我就自己封装了一个:
// SafeObjectList 线程安全的容器
#include <list>
#include "../Threading/CriticalSection.h"
 
plate<class T> class SafeObjectList : CriticalSection
{
private:
list<T> inner_list ;
list<T>::iterator itr ;
public:
void Add(T obj)
{
        this->Lock() ;
        this->inner_list.push_back(obj) ;
        this->Unlock() ;
}
void Remove(T obj)
{
       
        this->Lock() ;
        for(this->itr = this->inner_list.begin() ;this->itr != this->inner_list.end() ;this->itr++)
        {
               if(obj == (*(this->itr)))
               {
                      this->inner_list.erase(this->itr) ;
                      break ;
               }
        }
        this->Unlock() ;
       

void Clear()
{
        this->Lock() ;
        this->inner_list.clear() ;
        this->Unlock() ;
}
int Count()
{
        return (int)this->inner_list.size() ;

BOOL Contains(T& target)
{
        BOOL found = FALSE ; 
        this->Lock() ;
        for(this->itr = this->inner_list.begin() ;this->itr != this->inner_list.end() ;this->itr++)
        {
               if(target == (*(this->itr)))
               {
                      found = TRUE ;
                      break ;
               }
        }
        this->Unlock() ; 
        return found ; 

BOOL GetElement(int index ,T& result)
{
        BOOL succeed = FALSE  ;
        this->Lock() ;       
        if(index < (int)this->inner_list.size())
        {
               int i= 0 ;
               for(this->itr = this->inner_list.begin() ;this->itr != this->inner_list.end() ;this->itr++)
               {
                      if(i == index)
                      {
                             result =  (*this->itr) ;
                             break ;
                      }
                      i++ ;
               }    
              
               succeed = TRUE ;
        }
        this->Unlock() ;
       
        return succeed ;           
}    
};
 
       在 将临界资源与对其的操作封装在一个类中的时候,我们特别要需要注意的一点是封装的线程安全的方法(函数)的粒度,粒度太大则难以复用,粒度太小,则可能会 导致锁的嵌套。所以在封装的时候,一定要根据你的具体应用,视情况而定。我的经验是这样的,首先可以把粒度定小一点,但是一旦发现有锁的嵌套出现,就加大 粒度,把这两个嵌套合并成一个稍微粗一点粒度的方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  多线程