使用互斥锁的正确方法

Posted

技术标签:

【中文标题】使用互斥锁的正确方法【英文标题】:Correct Way to Use a Mutex 【发布时间】:2012-09-25 21:53:54 【问题描述】:

关于下面的代码,我想知道的是:在方法调用周围使用 try catch 块是一种好的做法。下面的代码中有哪些愚蠢之处?

#ifndef TIMER_H
#define TIMER_H

#include <boost/bind/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/function.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/recursive_mutex.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

#include <stdint.h>
#include <iostream>
#include <vector>

template <uint32_t tMilliSeconds>
class Timer 
private:
    static Timer *_instance;
    uint32_t mMilliSeconds;
    boost::mutex mListMutex;
    boost::thread mTimerThread;
    std::vector<boost::function<void()> > mHandlerList;

    Timer();
    Timer(const Timer &other);
    Timer &operator=(const Timer &other);
    void Run();

public:
    ~Timer();
    static boost::shared_ptr<Timer<tMilliSeconds> > Instance();
    void AddHandler(boost::function<void()> tmpBoostFunction);
;

template <uint32_t tMilliSeconds>
Timer<tMilliSeconds>::Timer() :
    mListMutex() 
    mMilliSeconds = tMilliSeconds;

    mTimerThread = boost::thread(
        boost::bind(&Timer<tMilliSeconds>::Run, this));


template <uint32_t tMilliSeconds>
Timer<tMilliSeconds>::~Timer()  
    mListMutex.lock();
    try 
        mTimerThread.detach();
     catch (...) 
        mListMutex.unlock();
    
    mListMutex.unlock();


template <uint32_t tMilliSeconds>
boost::shared_ptr<Timer<tMilliSeconds> >
Timer<tMilliSeconds>::Instance() 
    if (!_instance) 
        _instance = new Timer<tMilliSeconds>();
    
    return boost::shared_ptr<Timer<tMilliSeconds> >(_instance);


template <uint32_t tMilliSeconds>
void Timer<tMilliSeconds>::Run() 
    while(true) 
        boost::this_thread::sleep(
            boost::posix_time::milliseconds(mMilliSeconds));
        mListMutex.lock();
        for (std::vector<boost::function<void()> >::iterator vect_it =
            mHandlerList.begin(); vect_it != mHandlerList.end();
            ++vect_it) 
            try 
                (*vect_it)();
             catch (...) 
                mListMutex.unlock();
            
        
        mListMutex.unlock();
    


template <uint32_t tMilliSeconds>
void Timer<tMilliSeconds>::AddHandler(
    boost::function<void()> tmpBoostFunction) 

    mListMutex.lock();
    try 
        mHandlerList.push_back(tmpBoostFunction);
     catch (...) 
        mListMutex.unlock();
    
    mListMutex.unlock();

#endif // TIMER_H

【问题讨论】:

这属于codereview.stackexchange.com。 这不是评论,它是一个明确的代码问题。 好的,那么“其他愚蠢”的帮助在哪里?一个永远无法接受的单独答案?那好吧。您的 shared_ptr 用法不正确...从每个调用的普通指针创建一个新的 shared_ptr 意味着您的每个 shared_ptr 返回实际上都不会被共享。将 instance 存储为 shared_ptr 。我不认为detach() 做你认为它做的事情。大概你想退出线程。为此,您需要一个终止条件(或中断),以及析构函数中的 join() 【参考方案1】:

由于您使用的是 boost,我会考虑将互斥锁与boost::scoped_lock 结合使用,这样当scoped_lock 对象超出范围时,互斥锁会通过其析构函数调用“自动”解锁。然后,您无需担心将互斥锁与trycatch 块交错解锁,因为通过异常展开堆栈将通过scoped_lock 对象释放互斥锁上的锁。

【讨论】:

这个“作用域互斥锁”被一些人称为 AutoMutex。在 Qt 中,它是一个 QMutexLocker。 @Jason 作用域互斥锁导致我的程序锁定,因为我的实现没有。这可能是什么原因? 如果问题是重入,那么请确保使用带有scoped_lock 对象的重入类型互斥锁。作用域锁与互斥锁类型是分开的,因此这种抽象类型适用于这些场景。 我在您的代码中看不到任何地方,其中一个持有锁的函数会调用另一个也需要锁的函数。 scoped_lock 实现将与您的代码完全相同,除了对错误进行正确操作。也许您的 scoped_lock 用法不正确?【参考方案2】:

不,您对 catch 块的使用是错误的。如果确实发生了异常,那么您应该会导致更多的解锁。如果lock() 成功,则调用unlock() 一次且仅一次。您应该使用为您管理解锁的锁包装器,例如:

class mutex_lock

private:
    boost::mutex &mListMutex; 
public:
    mutex_lock(boost::mutex &aListMutex) : mListMutex(aListMutex)  mListMutex.lock(); 
    ~mutex_lock()  mListMutex.unlock(); 
;

那么你可以这样做:

template <uint32_t tMilliSeconds> 
Timer<tMilliSeconds>::~Timer()
 
    mutex_lock lock(mListMutex); 
    mTimerThread.detach(); 
 

template <uint32_t tMilliSeconds> 
void Timer<tMilliSeconds>::Run()
 
    while(true)  
        boost::this_thread::sleep(
            boost::posix_time::milliseconds(mMilliSeconds)); 
        mutex_lock lock(mListMutex); 
        for (std::vector<boost::function<void()> >::iterator vect_it = mHandlerList.begin(); vect_it != mHandlerList.end(); ++vect_it)  
            (*vect_it)(); 
         
     
 

template <uint32_t tMilliSeconds> 
void Timer<tMilliSeconds>::AddHandler( 
    boost::function<void()> tmpBoostFunction)
 
    mutex_lock lock(mListMutex); 
    mHandlerList.push_back(tmpBoostFunction); 
 

更新: boost 有自己的 scoped_lock 类,目的完全相同:

#include <boost/interprocess/sync/scoped_lock.hpp>

template <uint32_t tMilliSeconds> 
Timer<tMilliSeconds>::~Timer()
 
    boost::interprocess::scoped_lock<boost::mutex> lock(mListMutex); 
    mTimerThread.detach(); 
 

template <uint32_t tMilliSeconds> 
void Timer<tMilliSeconds>::Run()
 
    while(true)  
        boost::this_thread::sleep(
            boost::posix_time::milliseconds(mMilliSeconds)); 
        boost::interprocess::scoped_lock<boost::mutex> lock(mListMutex); 
        for (std::vector<boost::function<void()> >::iterator vect_it = mHandlerList.begin(); vect_it != mHandlerList.end(); ++vect_it)  
            (*vect_it)(); 
         
     
 

template <uint32_t tMilliSeconds> 
void Timer<tMilliSeconds>::AddHandler( 
    boost::function<void()> tmpBoostFunction)
 
    boost::interprocess::scoped_lock<boost::mutex> lock(mListMutex); 
    mHandlerList.push_back(tmpBoostFunction); 
 

【讨论】:

scoped_lock 导致了我担心的事情,这是一个非递归互斥锁开始多次锁定。我的代码不会导致这种情况。 您没有在显示的代码中使用boost::recursive_mutex,即使您包含了recursive_mutex.hpp 头文件。我不知道boost::mutexboost::recursive_mutex 之间的区别,但至少在Windows 上,boost::mutex 映射到CRITICAL_SECTION,可以在同一个线程上下文中递归锁定。

以上是关于使用互斥锁的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

互斥锁的示例

Mutex互斥锁的使用方法

使用条件变量优于互斥锁的优点

《并发系列二》自己动手实现互斥锁

synchronized关键字的使用及互斥锁的实现

synchronized互斥锁实例解析