Locking a mutex in a destructor in C++11

佐手、 提交于 2019-12-03 00:36:17

As suggested by Adam H. Peterson, I finally decided to write a no throw mutex :

class NoThrowMutex{
private:
    std::mutex mutex;
    std::atomic_flag flag;
    bool both;
public:
    NoThrowMutex();
    ~NoThrowMutex();
    void lock();
    void unlock();
};

NoThrowMutex::NoThrowMutex():mutex(),flag(),both(false){
    flag.clear(std::memory_order_release);}

NoThrowMutex::~NoThrowMutex(){}

void NoThrowMutex::lock(){
    try{
        mutex.lock();
        while(flag.test_and_set(std::memory_order_acquire));
        both=true;}
    catch(...){
        while(flag.test_and_set(std::memory_order_acquire));
        both=false;}}

void NoThrowMutex::unlock(){
    if(both){mutex.unlock();}
    flag.clear(std::memory_order_release);}

The idea is to have two mutex instead of only one. The real mutex is the spin mutex implemented with an std::atomic_flag. This spin mutex is protected by a std::mutex which could throw.

In a normal situation, the standard mutex is acquired and the flag is set with a cost of only one atomic operation. If the standard mutex cannot be locked immediately, the thread is going to sleep.

If for any reason the standard mutex throws, the mutex will enter its spin mode. The thread where the exception occured will then loop until it can set the flag. As no other thread is aware that this thread bybassed completely the standard mutex, they could spin too.

In the worst case scenario, this locking mechanism degrades to a spin lock. Most of the time it reacts just like a normal mutex.

This is a bad situation to be in. Your destructor is doing something that might fail. If failure to update this counter will irrecoverably corrupt your application, you may want to simply let the destructor throw. This will crash your application with a call to terminate, but if your application is corrupted, it may be better to kill the process and rely on some higher-level recovery scheme (such as a watchdog for a daemon or retrying execution for another utility). If failure to decrement the counter is recoverable, you should absorb the exception with a try{}catch() block and recover (or potentially save information for some other operation to eventually recover). If it's not recoverable, but it's not fatal, you may want to catch and absorb the exception and log the failure (being sure to log in an exception-safe way, of course).

It would be ideal if the code could be restructured such that the destructor doesn't do anything that can't fail. However, if your code is otherwise correct, failure while acquiring a lock is probably rare except in cases of constrained resources, so either absorbing or aborting on failure may very well be acceptable. For some mutexes, lock() is probably a no-throw operation (such as a spinlock using atomic_flag), and if you can use such a mutex, you can expect lock_guard to never throw. Your only worry in that situation would be deadlock.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!