锁定互斥在在C语言的析构函数++ 11(Locking a mutex in a destructo

2019-09-03 09:22发布

我有一些代码,需要是线程安全的,异常安全。 下面的代码是我的问题的一个非常简化的版本:

#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;}

我有一个互斥体,我需要在一个对象的析构函数来锁定。 问题是,我的析构函数不应该抛出异常。

我能做什么 ?

0),我不能没有n与原子变量(当然它会在这里做的伎俩,但不是我的问题点)

1)我可以用自旋锁代替我的互斥

2)我可以尝试捕捉锁定陷入无限循环,直到我eventualy收购也不例外提出的锁

这些解决方案都没有,似乎非常有吸引力。 你是否也有同样的问题? 你是如何解决的呢?

Answer 1:

正如亚当·彼得森H.建议,我终于决定写一个不抛互斥:

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);}

这个想法是有两个互斥的,而不是只有一个。 真正的互斥与实现自旋互斥std::atomic_flag 。 这种自旋互斥,由保护std::mutex这可能抛出。

在正常情况下,标准的互斥被获取并设置了标志只有一个原子操作的成本。 如果标准互斥不能被立即锁定,该线程进入休眠状态。

如果由于某种原因,标准的互斥抛出,互斥体将进入旋转模式。 当发生异常会然后循环,直到它可以设置标志的线程。 由于没有其他线程知道,这个线程bybassed完全标准的互斥体,它们可以旋转了。

在最坏的情况下,这种锁定机制降低到一个自旋锁。 大多数时候,它的反应就像一个正常的互斥。



Answer 2:

这是一个糟糕的情况是在你的析构函数做的事情可能会失败。 如果未能及时更新这个计数器将无可挽回地破坏你的应用程序,你可能想干脆让析构函数抛出。 这将调用你的程序崩溃terminate ,但如果您的应用程序已损坏,它可能是更好杀死进程,并依靠一些更高级别的恢复方案(如后台进程或重试运行另一程序看门狗) 。 如果未能递减计数器是可恢复的,你应该吸收例外与try{}catch()块和恢复(或潜在的信息保存一些其它的操作,最终恢复)。 如果这是不可能收回的,但它不是致命的,你可能要赶上和吸收异常并记录故障(即一定要登录一个异常安全的方式,当然)。

这将是理想的,如果该代码可以重组使得析构函数并不做任何事情,不能失败。 但是,如果你的代码是正确的,否则,失败而获取锁是除了在有限资源的情况下的可能很少,所以无论是吸收或中止对失败很可能是可以接受的。 对于一些互斥锁()可能是一个不抛的操作(如自旋锁使用atomic_flag),如果你可以使用这样一个互斥体,你可以期望lock_guard永远不会抛出。 你在这种情况下唯一担心的是僵局。



文章来源: Locking a mutex in a destructor in C++11