如何在动态容器中锁定可变数量的互斥锁?

问题描述 投票:0回答:1

类似问题:

用 std::vector 调用 std::lock ()
使用 std::lock_guard 数组锁定 std::mutex 数组

总结: 第二个显示锁定静态大小的数组,第一个指的是 boost 库 boost::lock() ,它接受两个迭代器。然而,boost::lock 不支持 RAII,而 std::lock 则不支持。我正在寻找如下所示的解决方案

std::lock(m1, m2);
std::lock_guard l1{m1, std::adopt_lock};
std::lock_guard l2{m2, std::adopt_lock};

或者是否需要所有权转让

std::unique_lock<std::mutex> l1{m1, std::defer_lock};
std::unique_lock<std::mutex> l2{m2, std::defer_lock};
std::lock(l1, l2);

或使用 std::scoped_lock

std::scoped_lock<std::mutex> lk{ m1, m2 };

但是,需要锁定的互斥锁属于运行时已知的多个对象。因此,我不能使用 std::lock 或 std::scoped_lock。

std::vector<std::mutex> mutexes{};
mutexes.push_back(std::move(m1));
mutexes.push_back(std::move(m2));
std::scoped_lock<std::mutex> lk{ mutexes }; // ERROR

项目:

我有一个以 DAG 结构为中心的项目。 DAG 包含多对一和一对多关系,并且(理应如此)支持祖先方向和后代方向的遍历。为了防止死锁,我尝试尽可能仅在一个方向(后代)实现所有算法。然而,在某些时候,代码不可避免地包含祖先方向。因此,我尝试在遍历结构时一次性锁定相关的互斥锁。例如,在下降方向:

DAGNode DN{};
std::vector<DAGNode> descendants{ DN.get_descendants() };

// Assume we have two descendants
auto& des1{ descendants[0] };
auto& des2{ descendants[1] };
std::scoped_lock<std::mutex> lk{DN.m, des1.m, des2.m};

祖先方向的代码类似。

由于DAG同时支持多对一和一对多关系,因此DAG强烈要求必须像上面一样一起执行锁定。对我来说,由于多对一和一对多关系,定义总顺序并按该顺序锁定看起来并不合理。例如,考虑节点 A1、A2、B1、B2、B3,其中 A1 是 B1 和 B2 的祖先; A2 是 B2 和 B3 的祖先。所以当A1参与算法时,(A1,B1,B2)必须被锁定,而A2必须分别锁定(A2,B2,B3)。因此,B2必须被锁定在分别从A1和A2开始的两次遍历中。这是多对一关系的结果,但一对多也会导致类似的情况。实际上,从这个简单的例子可以看出,同方向的遍历也可能会导致死锁(由于B2)。因此,唯一的解决方案是立即锁定所有互斥体。

总而言之,我的问题是:

  1. C++ 不支持锁定动态数量的互斥锁(?)
  2. Boost 可以,但在 RAII 庄园中没有(?)
  3. 如果前两个正确的话,如何在多线程代码中管理 DAG 的多对一和一对多关系,同时防止死锁?
  4. (虽然对我来说似乎不合理)是否还有其他方法/途径,例如应用总订单
  5. 我重温了《Concurrency In Action》、《Effective Modern C++》等著名书籍。你能给我推荐其他资源吗? 非常感谢。

c++ multithreading boost deadlock
1个回答
0
投票
  1. 不是一站式 API,但您可以:

  2. #include <boost/thread.hpp> #include <mutex> struct bulk_guard { std::list<std::unique_lock<std::mutex>> guards_; template <typename Lockables> explicit bulk_guard(Lockables& lockables) { boost::lock(lockables.begin(), lockables.end()); for (auto& lockable : lockables) guards_.emplace_back(lockable, std::adopt_lock); } }; #include <array> int main() { std::array<std::mutex, 10> mxs; bulk_guard bulk(mxs); }
  3. 这是一个难题,我通常可以通过锁定更粗粒度的组(例如 DDD 中的聚合,但我在各种软件中识别这些实体;通常它是一个分配单位(例如“任务”)或所有权单位(例如文件系统、目录、进程/线程等)
  4. 我相信这已经是

    boost::lock
  5. 算法的基础了。这
  6. 合理的。在大多数实现(包括 POSIX)上,可锁定实现

    不可移动
    ,这意味着 C++ 对象标识(通俗地说“地址”)可以充当总顺序。 我也活过那本书。我认为它是最好的资源之一,尽管它似乎主要集中在基于任务的并发性上,而且您似乎更多地从基于参与者的并发范例中获得它。我对此并不热衷(我认为它往往与性能相反,这让我想起了许多失败的面向参与者的并发性的承诺,例如 Corba 和 COM+ - 配有

    MTS
  7. )。
  8. 我知道你可能可以在 Eiffel/Smalltalk 文献中找到更多关于这个观点的信息,但它不再那么流行可能是有原因的。

© www.soinside.com 2019 - 2024. All rights reserved.