【问题标题】:Locking a mutex in a destructor in C++11在 C++11 的析构函数中锁定互斥锁
【发布时间】:2013-05-15 15:20:54
【问题描述】:

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

#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) 我可以尝试将锁定捕获到一个无限循环中,直到我最终获得锁定而没有引发异常

这些解决方案似乎都不是很吸引人。你有同样的问题吗?你是怎么解决的?

【问题讨论】:

  • I have a mutex that I need to lock in the destructor of an object -- 听起来是个坏主意。与其向我们提供解决方案并让我们用它来解决问题,不如告诉我们您要解决什么问题,以便我们提供更好的解决方案。
  • @RobertHarvey 我真正想做的是将修改保存在数据库中后插入共享缓存。

标签: c++ thread-safety destructor raii exception-safety


【解决方案1】:

按照 Adam H. Peterson 的建议,我最终决定编写一个 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);}

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

在正常情况下,获取标准互斥体并设置标志,成本仅为一次原子操作。如果标准互斥锁不能立即锁定,线程将进入休眠状态。

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

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

【讨论】:

    【解决方案2】:

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

    如果可以重构代码以使析构函数不做任何不会失败的事情,那将是理想的。但是,如果您的代码在其他方面是正确的,那么在获取锁时失败可能很少见,除非在资源受限的情况下,因此吸收或中止失败很可能是可以接受的。对于某些互斥锁,lock() 可能是一个无抛出操作(例如使用 atomic_flag 的自旋锁),如果您可以使用这样的互斥锁,您可以期望 lock_guard 永远不会抛出。在这种情况下,您唯一担心的是死锁。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-31
      • 2015-03-21
      • 2016-04-27
      • 1970-01-01
      相关资源
      最近更新 更多