简体   繁体   English

在C ++ 11中锁定析构函数中的互斥锁

[英]Locking a mutex in a destructor in C++11

I have some code which need to be thread safe and exception safe. 我有一些代码需要线程安全和异常安全。 The code below is a very simplified version of my problem : 下面的代码是我的问题的一个非常简化的版本:

#include <mutex>
#include <thread>

std::mutex mutex;
int n=0;

class Counter{
public:
    Counter(){
        std::lock_guard<std::mutex>guard(mutex);
        n++;}
    ~Counter(){
        std::lock_guard<std::mutex>guard(mutex);//How can I protect here the underlying code to mutex.lock() ?
        n--;}
};

void doSomething(){
    Counter counter;
    //Here I could do something meaningful
}

int numberOfThreadInDoSomething(){
    std::lock_guard<std::mutex>guard(mutex);
    return n;}

I have a mutex that I need to lock in the destructor of an object. 我有一个互斥锁,我需要锁定一个对象的析构函数。 The problem is that my destructor should not throw exceptions. 问题是我的析构函数不应该抛出异常。

What can I do ? 我能做什么 ?

0) I cannot replace n with an atomic variable (of course it would do the trick here but that is not the point of my question) 0)我不能用原子变量替换n (当然它会在这里做的但是这不是我的问题的重点)

1) I could replace my mutex with a spin lock 1)我可以用旋转锁替换我的互斥锁

2) I could try and catch the locking into an infinite loop until I eventualy acquire the lock with no exception raised 2)我可以尝试将锁定捕获到无限循环中,直到我最终获得锁定而没有异常引发

None of those solution seems very appealing. 这些解决方案似乎都没有吸引力。 Did you have the same problem ? 你有同样的问题吗? How did you solve it ? 你是怎么解决的?

As suggested by Adam H. Peterson, I finally decided to write a no throw mutex : 正如Adam H. Peterson所说,我最终决定编写一个无抛出的互斥体:

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 . 真正的互斥锁是使用std::atomic_flag实现的自旋互斥锁。 This spin mutex is protected by a std::mutex which could throw. 这个自旋互斥锁受到std::mutex保护,它可以抛出。

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). 这将使您的应用程序通过调用terminate而崩溃,但如果您的应用程序已损坏,则最好terminate该进程并依赖某些更高级别的恢复方案(例如守护程序的守护程序或重试另一个实用程序的执行) 。 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). 如果计数器的递减失败是可恢复的,则应使用try{}catch()块吸收异常并恢复(或者可能保存其他操作的信息以最终恢复)。 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. 对于某些互斥锁,lock()可能是一个无抛出操作(例如使用atomic_flag的自旋锁),如果你可以使用这样的互斥锁,你可以期望lock_guard永远不会抛出。 Your only worry in that situation would be deadlock. 在这种情况下你唯一的担忧就是陷入僵局。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM