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

Arn*_*aud 6 c++ destructor raii thread-safety exception-safety

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

#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;}
Run Code Online (Sandbox Code Playgroud)

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

我能做什么 ?

0)我不能n用原子变量替换(当然它会在这里做的但是这不是我的问题的重点)

1)我可以用旋转锁替换我的互斥锁

2)我可以尝试将锁定捕获到无限循环中,直到我最终获得锁定而没有异常引发

这些解决方案似乎都没有吸引力.你有同样的问题吗?你是怎么解决的?

Arn*_*aud 10

正如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);}
Run Code Online (Sandbox Code Playgroud)

这个想法是有两个互斥锁而不是一个互斥锁.真正的互斥体是用一个实现的自旋互斥体std::atomic_flag.这个自旋互斥锁受到std::mutex可能抛出的保护.

在正常情况下,获取标准互斥锁,并且仅以一个原子操作的成本设置标志.如果无法立即锁定标准互斥锁,则线程将进入休眠状态.

如果由于任何原因标准互斥锁抛出,互斥锁将进入其旋转模式.发生异常的线程将循环,直到它可以设置标志.由于没有其他线程知道这个线程完全是标准的互斥体,它们也可以旋转.

在最坏的情况下,这种锁定机制降级为旋转锁定.大多数时候它的反应就像普通的互斥锁一样.


Ada*_*son 5

这是一个糟糕的情况。您的析构函数正在做可能会失败的事情。如果无法更新此计数器将无法恢复破坏您的应用程序,则可能只需要让析构函数抛出即可。这将通过调用来使您的应用程序崩溃terminate,但是如果您的应用程序已损坏,则最好终止该进程并依赖某些更高级别的恢复方案(例如守护程序的看门狗或其他实用程序的重试执行)。如果递减失败的计数器是可恢复的,则应使用try{}catch()阻止并恢复(或可能保存信息以供其他操作最终恢复)。如果它不是可恢复的,但不是致命的,则您可能想捕获并吸收异常并记录故障(当然,请务必以异常安全的方式进行记录)。

如果可以对代码进行重组以使析构函数不做任何无法失败的事情,那将是理想的。但是,如果您的代码正确无误,则除非资源有限,否则获取锁时失败的情况可能很少,因此完全可以接受或中止失败。对于某些互斥锁,lock()可能是不抛出操作(例如,使用atomic_flag的自旋锁),如果可以使用这种互斥锁,则可以期望lock_guard永远不会抛出异常。在这种情况下,您唯一担心的是僵局。