用C++封装线程的互斥操作(linux)

学过操作系统的人,估计都知道互斥量是个何方神圣,我这里也就不再狗尾续貂再做解释了

好,先看Linux下关于互斥量的一些API(所谓封装,说白了也就是封装这些API,让这些API更简单好用罢了)

一、Linux下互斥量的使用

1、定义一个互斥量

pthread_mutex_t  myMutex;

2、初始化互斥量

pthread_mutex_init( &myMutex, 0);  //第2个参数是设置互斥量的属性,如果要使用默认值就设置成0

返回值:成功返回0 ,错误返回错误号

3、使用互斥量

pthread_mutex_lock(&myMutex);  //阻塞的方式加锁

pthread_mutex_trylock(&myMutex);  //非阻塞的方式加缩

pthread_mutex_unlock(&myMutex); //解锁

返回值:成功返回0,错误返回错误编号

4、销毁互斥量

pthread_mutex_destroy(&myMutex);


二、封装互斥量的方案一

[cpp]  view plain  copy
  1. class CMutex  
  2. {  
  3.   private:  
  4.     
  5.   pthread_mutex_t  m_Mutex;  
  6.   
  7.   public:  
  8.   
  9.   CMutex()  
  10.   {  
  11.     int r = pthread_mutex_init(&m_Mutex,0);  
  12.     if(r != 0)  
  13.     {  
  14.     //错误处理  
  15.     }  
  16.   }  
  17.   
  18.   ~CMutex()  
  19.   {  
  20.     int r = pthread_mutex_destroy(&m_Mutex);  
  21.     if(r != 0)   
  22.     {  
  23.     //参数检查  
  24.     }  
  25.   }  
  26.   CStatus Lock()  
  27.   {  
  28.     int r = pthread_mutex_lock(&m_Mutex);  
  29.     ...  
  30.   }  
  31.   CStatus Unlock()  
  32.   {  
  33.     int r = pthread_mutex_unlock(&m_Mutex);  
  34.     ...  
  35.   }  
  36. };  

使用方法是,

[cpp]  view plain  copy
  1. //为了方便操作,先把共享资源和互斥量绑在一个结构体中  
  2.   
  3. struct Data  
  4. {  
  5.   int sharedSource;   
  6.   CMutex mutex;  
  7. };  
  8.   
  9. int main()  
  10. {  
  11.   ...  
  12.   struct Data data;    
  13.   data.mutex.Lock();  
  14.   
  15.   data.sharedSource++;  
  16.   
  17.   data.mutex.Unlock();    
  18.   ...  
  19. }  


方案一的问题:

其实正常情况下,方案一是可以满足我们的需要的,但是当程序出错的时候,方案一就不能再胜任工作了,为什么???

考虑这样一种情况,

[cpp]  view plain  copy
  1. void test()  
  2. {  
  3.   throw 1;  
  4. }  //测试函数专门用来模拟程序出现异常  
  5.   
  6. void func()  
  7. {  
  8.   
  9.    try  
  10.    {  
  11.      struct Data data;    
  12.      data.mutex.Lock();  //1  
  13.        
  14.      test();             //2  
  15.   
  16.      data.mutex.Unlock();//3  
  17.   
  18.    }  
  19.    catch(int)  
  20.    {  
  21.      ....  
  22.    }  
  23. }  


好了,分析上面的代码,在标志1处,我们给互斥量加锁,然后在标志2处,程序产生了一个异常,as we all know,在try 中产生异常后,程序会在异常代码处终止执行,然后直接跳转到catch块中进行错误处理。

问题来了,当标志2处产生异常后,标志3处代码就不能执行,换句话说,我们不能再进行解锁操作了..

可能有人说,“这好说,我们在catch块中直接再加上一行解锁代码,不就得了吗”,

恩,确实,但是如果我再告诉你,我们在互斥量加锁前(即标志//1之前)或在解锁后(//3 之后)出现了错误,你的解决方法就显得有点太不专业了


好废话半天,拒绝再卖关子,看看土豪们都是怎么封装互斥量吧

线程同步封装方案二:

[cpp]  view plain  copy
  1. class CEnterCriticalSection  
  2. {  
  3.   
  4.    private:  
  5.      
  6.    CMutex * m_pMutex;  
  7.   
  8.    public:  
  9.   
  10.    CEnterCriticalSection(CMutex * pMutex)  
  11.    {  
  12.      ....//参数检查  
  13.   
  14.      m_pMutex = pMutex;  
  15.   
  16.      CStatus s = m_pMutex->Lock();  
  17.   
  18.      ...//返回值s 检查  
  19.    }  
  20.   
  21.    ~CEnterCriticalSection()  
  22.    {  
  23.       CStatus s = m_pMutex->Unlock();  
  24.       ...//返回值s检查  
  25.    }  
  26. };  

新版本的互斥量封装是两个类的组合 即 : CMutex(提供互斥量的基本操作) + CEnterCriticalSection(提供互斥量的简单、安全操作)

这个版本的秒处就在于,我们在CEnterCriticalSection的析构函数中调用了互斥量的解锁函数,说起来可能比较晦涩,先看看新版的封装怎么处理

方案一中遇到的问题

[cpp]  view plain  copy
  1.   
[cpp]  view plain  copy
  1. void func()  
  2. {  
  3.    try  
  4.    {  
  5.      struct Data data;  
  6.        
  7.      CEnterCriticalSection ecs(&(data.mutex));  
  8.        
  9.      data->sharedSource++;  
  10.      test()  
  11.    }  
  12.    catch(..)  
  13.    {  
  14.   
  15.    }  
  16. }  
或者在其他环境我们可以使用 {  } 来确定加锁区域.eg:

[cpp]  view plain  copy
  1. void otherFunc()  
  2. {  
  3.   ...  
  4.     
  5.   //使用{}来确定加锁区域  
  6.   {  
  7.      CEnterCriticalSection ecs(&(data.mutex));  
  8.      ....  
  9.   } //局部变量ecs 的生命周期在这里“}”结束,然后ecs调用自己的析够函数,并在析构函数中解锁,  
  10.     
  11.   ...  
  12. }  

分析:

方案二的优点是

1、不许要人为的调用解锁函数unlock

2、操作简单,只需要在加锁区域的开头创造一个局部对象,然后使用{  } 规定该局部变量的生存周期,也就规定了加锁区域的范围

猜你喜欢

转载自blog.csdn.net/IOT_SHUN/article/details/80654811