boost中的mutex与lock

最近在多线程编程中多次使用到mutex来互斥,看了下项目的代码,有自己封装的mutex类,也有直接使用boost::mutex的,而boost中关于mutex可谓令人眼花撩换。这里总结一下。

对于mutex和lock,要明确一点,真正起到互斥作用的mutex,而lock可以认为是协助mutex令我们在使用时更方便。搞不清楚二者关系的可以参考这里:从高中一次半夜不冲厕所的经历谈程序

最基础的mutex的使用方法是这样的:

[html] view plaincopy

  1. HANDLE g_mutex = NULL; 
  2. void test() 
  3.     ::WaitForSingleObject(g_mutex, INFINITE); 
  4.     //do something... 
  5.     ReleaseMutex(g_mutex); 

使用匿名的互斥体,test函数中调用WaitForSingleObject等待其他线程释放g_mutex。INFINITE表示一直等待,也可以设置等待超时时间。

当前线程获得g_mutex后执行do something,之后释放g_mutex。

这里使用的是匿名互斥体,当然,在程序中需要多个互斥体时,可以通过CreateMutex创建命名互斥体,也可以通过OpenMutex打开一个互斥体。

不过很少有人像上面那样直接使用吧,太简单粗暴了,互斥体作为一个基本功能模块,怎么说也会封装一下,让别人用起来更爽些。如下是一个简单封装:

[html] view plaincopy

  1. class MyMutex 
  2. public: 
  3.     MyMutex() 
  4.         :m_hMutex(NULL) 
  5.     { 
  6.     } 
  7.     MyMutex(wchar_t* pMutexName) 
  8.         :m_hMutex(NULL) 
  9.     { 
  10.         createMutex(pMutexName); 
  11.     } 
  12.     virtual ~MyMutex() 
  13.     { 
  14.        destroyMutex(); 
  15.     } 
  16.     bool lock() 
  17.     { 
  18.         return m_hMutex ? (::WaitForSingleObject(m_hMutex,INFINITE) == WAIT_OBJECT_0) : false; 
  19.     } 
  20.     void unlock() 
  21.     { 
  22.         ReleaseMutex(m_hMutex); 
  23.     } 
  24.     bool createMutex(wchar_t* pMutexName) 
  25.     { 
  26.         if (m_hMutex) 
  27.         { 
  28.             return true; 
  29.         } 
  30. m_hMutex = ::CreateMutex(NULL, FALSE, pMutexName); 
  31.         return m_hMutex != NULL; 
  32.     } 
  33.     void destroyMutex() 
  34.     { 
  35.         CloseHandle(m_hMutex); 
  36. m_hMutex = NULL; 
  37.     } 
  38.     bool openMutex(wchar_t* pMutexName) 
  39.     { 
  40.         if (m_hMutex) 
  41.         { 
  42.             return true; 
  43.         } 
  44. m_hMutex = ::OpenMutex(SYNCHRONIZE, FALSE, pMutexName); 
  45.         return m_hMutex != NULL; 
  46.     } 
  47. private: 
  48.     HANDLE m_hMutex; 
  49. }; 

封装之后,就可以如下使用:

[html] view plaincopy

  1. void test1() 
  2.     MyMutex mutex; 
  3.     mutex.createMutex(L"mutex_test_name1"); 
  4.     if (mutex.lock()) 
  5.     { 
  6.         //do something... 
  7.         mutex.unlock(); 
  8.     } 

但是,就像从高中一次半夜不冲厕所的经历谈程序说的那样,释放锁就像”冲大便“一样让人容易忘记,如果在mutex.lock之后,忘记mutex.unlock,那么悲剧就发生了,当前线程将一直占据这把锁。。。。。

于是,便有了帮助类lock,或者叫做guard,或者lockguard等等吧。远离就是保证在离开作用域时,不需要手动调用unlock,而有人帮助我们做unlock的操作,省了很多事。一个帮助类lock大概是这样子的:

[html] view plaincopy

  1. class MyMutexLockGuard 
  2. public: 
  3.     MyMutexLockGuard(MyMutex* pMutex) 
  4.     { 
  5. m_pMutex = pMutex; 
  6.         if (m_pMutex) 
  7.         { 
  8.             m_pMutex->lock(); 
  9.         } 
  10.     } 
  11.     virtual ~MyMutexLockGuard() 
  12.     { 
  13.         if (m_pMutex) 
  14.         { 
  15.             m_pMutex->unlock(); 
  16.         } 
  17.     } 
  18. private: 
  19.     MyMutex* m_pMutex; 
  20. }; 

调用起来大概是这样的:

[html] view plaincopy

  1. void test2() 
  2.     MyMutex mutex(L"mutex_test_name2"); 
  3.     {//scope 1 
  4.         MyMutexLockGuard lock(&mutex); 
  5.         //do something... 
  6.     } 
  7.     //out of the scope 1, the mutex has been unlocked 

当离开作用域1时,lock对象被析构,自动调用mutex.unlock函数释放锁。是不是很爽...

需要注意的是CreateMutex和OpenMutex这两个windows api,CreateMutex的第二个参数bInitialOwner最好设为false。设为true时代表创建这个mutex的线程是直接获取这个mutex,相当于创建这个mutex的过程中调用了waitforsingleobject,因此即使后来lock和unlock配对调用,最后先启动的这个线程还是没有释放这个mutex,必须手动再调用一次unlock才行。因此设为false更稳妥些。OpenMutex的第一个参数安全属性最好设为SYNCHRONIZE ,win7和vista下不要用ALL_ACCESS,有可能失败。第二参数表示进程创建出的子进程是否可以直接继承该mutex。

上面的是mutex的基本用法,更强大的是boost中对于mutex和lock的实现。

boost中的mutex貌似有6种或者更多,我用过的有3中boost::mutex、boost::shared_mutex、boost::recursive_mutex,貌似还有boost::try_mutex、boost::time_mutex,不过没用过。

boost::mutex是最基础的锁,有lock和unlock方法,可以认为是互持锁。boost::shared_mutex是共享锁,有lock、unlock方法以及shared_lock、shared_unlock方法。boost::recursive_mutex是重入锁或者称为递归锁,这个最后再说。

boost::shared_mutex可以用来实现读写锁。多线程中的一个经典问题是一写多读,即当有线程发生写操作时,所有其他线程的读操作暂停,其他时刻,允许多个线程同时读操作。使用boost::shared_mutex构造读写锁时需要使用到boost中的lock帮助类系列(作用类似上面我写的MyMutexLockGuard)。boost::shared_lock<T>和boost::unique_lock<T>,从字面上看,shared_lock是共享的,unique_lock是独占的,shared_lock<T>只允许T是shared_mutex,而unique_lock<T>对T没有限制,如果T是shared_mutex,则在执行加锁和解锁时会调用shared_lock和shared_unlock方法,否则,则执行一般的lock和unlock方法。

实现读写锁:

[html] view plaincopy

  1. typedef boost::unique_lock<boost::shared_mutex> ReadLock; 
  2.      typdef boost::shared_lock<boost::shared_mutex> WriteLock; 
  3.      boost::shared_mutex  read_write_mutex; 
  4.      void _ReadThreadFunc() 
  5.      { 
  6.     ReadLock read_lock(read_write_mutex); 
  7.     //read data... 
  8.      } 
  9.      void _WriteThreadFunc() 
  10.      {   
  11.     WriteLock write_lock(read_write_mutex); 
  12.     //write data... 
  13.      } 

        使用boost::unique_lock和boost::mutex则可以实现最基本的独占时互斥

[html] view plaincopy

  1. boost::unique<boost::mutex> MyLock 
  2.      boost::mutex myMutex; 
  3.      void func() 
  4.     { 
  5.          MyLock lock(myMutex); 
  6.          // do something... 
  7.     } 

      *注意:boost::mutex::scoped_lock和boost::unique是一个东东哦...

还有个东西叫boost::lock_guard,它是比boost::unique更轻量级的lock。看下boost::lock_guard的源代码:

[html] view plaincopy

  1. template<typename Mutex>
  2.     class lock_guard 
  3.     { 
  4.     private: 
  5.         Mutex& m; 
  6.         explicit lock_guard(lock_guard&); 
  7.         lock_guard& operator=(lock_guard&); 
  8.     public: 
  9.         explicit lock_guard(Mutex& m_): 
  10.             m(m_) 
  11.         { 
  12.             m.lock(); 
  13.         } 
  14.         lock_guard(Mutex& m_,adopt_lock_t): 
  15.             m(m_) 
  16.         {} 
  17.         ~lock_guard() 
  18.         { 
  19.             m.unlock(); 
  20.         } 
  21.     }; 

可以看到只有两个public方法,即构造和析构函数,也就是说,使用boost::lock_guard去guard一个mutex,必然是在boost::lock_guard的对象离开其作用域时unlock它所guard的mutex,不提供提前unlock的功能。

而boost::unique_lock则提供这个功能,除了像boost::lock_guard一样在离开作用域时unlock它guard的mutex外,boost::unique还提供unlock函数,使用者可以手动执行unlock。此外,unique_lock还可以设置超时。

最后说下boost::recursive_mutex,先看下面的逻辑:main调用test3,test3中锁住g_mutex,调用test4,test4中尝试获得g_mutex,结果这个程序将死锁。因为test3锁住g_mutex后,在同一线程(更准确是同一堆栈中)再次尝试获得g_mutex,由于前面的锁未释放,这里将等待,形成死锁。

[html] view plaincopy

  1. boost::mutex g_mutex; 
  2. void test4() 
  3.     boost::lock_guard<boost::mutex> lock(g_mutex); 
  4.     //do something... 
  5. void test3() 
  6.     boost::lock_guard<boost::mutex> lock(g_mutex); 
  7.     test4(); 
  8.     //do something... 
  9. int _tmain(int argc, _TCHAR* argv[]) 
  10.     test3(); 
  11.     return 0; 

第二个例子:

[html] view plaincopy

  1. void * thread_func_one(void *arg)   
  2. {   
  3.    int i;   
  4.    for(i=0;i<10;i++){   
  5.      pthread_mutex_lock( &mutex1);   
  6.      pthread_mutex_lock( &mutex1);//锁两次   
  7.      count++;   
  8.      sleep(1);   
  9.      pthread_mutex_unlock(&mutex1);   
  10.      pthread_mutex_unlock(&mutex1);   
  11.      printf("thread one count value is %d/n",count);   
  12.    }   
  13.    return NULL;   
  14. }  

同样的道理,也死锁。那么对于这种在一个线程中可能在锁中需要再次获得锁的情况,就需要使用重入锁,boost::recursive_mutex。如下使用就没问题了。

[html] view plaincopy

  1. boost::recursive_mutex g_mutex; 
  2. void test4() 
  3.     boost::recursive_mutex::scoped_lock<boost::recursive_mutex > lock(g_mutex); 
  4.     //do something... 
  5. void test3() 
  6. boost::recursive_mutex::scoped_lock<boost::recursive_mutex > lock(g_mutex); 
  7.      test4();  
  8.     //do something... 
  9. int _tmain(int argc, _TCHAR* argv[]) 
  10.      test3(); 
  11.      return 0; 
posted @ 2013-03-21 16:27  JustinYo  阅读(3240)  评论(0编辑  收藏  举报