c++ - 在析构函数中获取锁是一个坏主意吗?

标签 c++ synchronization raii

假设我们有类似于以下伪代码的内容,其目标是实现并发并利用 RAII:

class Foo {
public:
    vector<int> nums;
    mutex lock;
};

class Bar {
public:
    Bar(Foo &foo) : m_foo(foo) 
    {
        lock_guard<mutex>(foo.lock);
        m_num = foo.nums.back();
        foo.nums.pop_back();
    }
    ~Bar()
    {
        lock_guard<mutex>(foo.lock);
        foo.nums.push_back(m_num);
    }
private:
    Foo &m_foo;
    int m_num;
};

然后,假设我们可能有任意数量的 Bar 实例,其想法是,当它们超出范围时,析构函数会将其持有的“资源”返回给 Controller Foo 类。然而,我们还需要确保线程安全,因此需要锁。然而,我对这种设计有点警惕,因为直观上在析构函数中使用互斥体似乎是一个坏主意。我是否想得太多了,或者如果不是,是否有更好的方法来利用 RAII?

最佳答案

在析构函数中锁定互斥锁本质上没有任何问题。例如,共享资源可能需要成为线程安全的。那么,释放共享资源的所有权可能需要锁定互斥体。如果 RAII 只是在多线程编程中崩溃,那么它就不会是一个非常有用的工具。确实,访问了std::shared_ptr的控制 block 是线程安全的,包括在共享指针销毁期间递减引用计数器时。显然,这通常是使用原子操作而不是互斥锁来实现的(不要引用我的话),但上下文是相同的:您在销毁期间释放共享资源的所有权,并且必须将其记录在线程安全的方式。

但是,请记住:锁定互斥体可能会引发异常,并且您应该(几乎)始终使用 try/catch 在析构函数中吸收异常。否则,如果堆栈已经由于另一个异常而展开,则程序将立即且不可恢复地终止,无论调用代码是否具备吸收原始异常和/或析构函数异常的能力。

但是可能有一种方法可以重组您的代码以完全避免该问题:A Bar实际上并不需要Foo 的引用;它只需要一个 int 。在您的代码中,Bar请求 int从给定的Foo 。当 Bar被破坏了,需要给int返回Foo以便可以回收利用;这需要存储对 Foo 的内部引用在其整个生命周期中,以便在破坏期间可以与其通信。 相反,请考虑给出 intBar直接在施工后,并采取int毁灭后远离它。这就是 dependency injection 背后的驱动原理,构成“SOILD”中的“D”。因此,它带来了依赖注入(inject)的所有典型优点(例如,提高 Bar 类的可测试性)。

例如,可以在管理 Foo 的更大类中跟踪此逻辑。对象及其所有关联的 Bar对象。这是一些伪代码,但确切的接口(interface)详细信息将取决于您的应用程序:

class BarPool:
    Foo foo;
    Map<int, Bar> bars;
    mutex m;

    BarPool(Foo foo) : foo(foo) {}

    int add_bar():
        lock m;
        // Note: foo.pop() should probably be made thread-safe
        // by internally locking / unlocking foo's mutex
        int i = foo.pop()
        bars.add(i, new Bar(i));
        unlock m;
        return i

    void remove_bar(int i):
        lock m;
        // foo.push() should also probably be made thread-safe
        bars.remove(i)
        foo.push(i)
        unlock m;

    ...

关于c++ - 在析构函数中获取锁是一个坏主意吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74008646/

相关文章:

c++ - cplusplus中char数组和string的区别

c++ - 递归的 Theta 运行时

c++ - pthread_cond_broadcast 之前的同步

java - 对于不改变的共享数据的 getter() 是否需要同步?

c++ - 使用 RAII 从 C 风格的 API 管理资源

c++ - 如何自动将流模式设置回默认值

c++ - 在 linux 中使用另一个共享库构建共享库

c++ - 将通过引用传递的值分配给成员变量(在 C++ 中)

multithreading - 如何在多线程游戏引擎中保持我的世界数据同步?

C++ RAII 管理对象状态的改变和恢复