将互斥锁存储在vector / deque c ++中

时间:2015-05-24 00:09:14

标签: c++11 mutex

我想在vector或deque等容器中存储可变数量的互斥锁。

在其中一个用例中,我需要可靠且无死锁地锁定所有互斥锁。我还希望有异常安全保证,如果抛出异常,所有互斥锁就好像没有发生锁定一样。

我正在尝试做类似的事情:

std::vector<std::mutex> locks_(n);
std::vector<std::lock_guard<std::mutex> > guards(n);
for(int i = 0; i < n; i++) {
    std::lock_guard<std::mutex> guard(locks_[i]);
    guards.emplace_back(std::move(guard));
}

但它没有编译,给我:

  

/ usr / include / c ++ / 4.8 / ext / new_allocator.h:120:4:错误:使用已删除   function'std :: lock_guard&lt; _Mutex&gt; :: lock_guard(const   std :: lock_guard&lt; _Mutex&gt;&amp;)[with _Mutex = std :: mutex]'

我想当lock_guards被销毁时也可能存在问题,因为与构造相比,顺序必须颠倒,但是标准为我们节省了:

  

delete-expression将调用析构函数(如果有的话)   对象或要删除的数组的元素。在一个案例中   数组,元素将按地址递减的顺序销毁   (也就是说,以完成构造函数的相反顺序;参见   12.6.2)。

这种方法是否存在任何潜在的缺陷?如何才能使其发挥作用?

修改

实际上我错了,似乎矢量并不能保证特定的破坏顺序。请参阅此问题:Order of destruction of elements of an std::vector

EDIT2

:如果用例是:

,该怎么办?

所有互斥锁都由不同的线程以任何顺序锁定/解锁(但是每个线程一次只使用1个互斥锁), 但在某些时候,我需要在另一个线程中以安全的方式锁定所有互斥锁。

2 个答案:

答案 0 :(得分:1)

n上有一个坚定而低的上限,你可以合理地做这样的事情:

#include <iostream>
#include <mutex>
#include <vector>

int
main()
{
    constexpr unsigned n_max = 5;
    unsigned n;
    std::cout << "Enter n: ";
    std::cin >> n;
    if (std::cin.fail())
        throw "oops";
    if (n > n_max)
        throw "oops";
    std::vector<std::mutex> mutexes(n);
    std::vector<std::unique_lock<std::mutex>> locks;
    for (auto& m : mutexes)
        locks.emplace_back(m, std::defer_lock);
    switch (locks.size())
    {
    case 0:
        break;
    case 1:
        locks.front().lock();
        break;
    case 2:
        std::lock(locks[0], locks[1]);
        break;
    case 3:
        std::lock(locks[0], locks[1], locks[2]);
        break;
    case 4:
        std::lock(locks[0], locks[1], locks[2], locks[3]);
        break;
    case 5:
        std::lock(locks[0], locks[1], locks[2], locks[3], locks[4]);
        break;
    default:
        throw "oops";
    }
}

不是那么漂亮。但它很容易推理并因此可靠。

注意:

  1. 您需要使用std::lock(m1, m2, ...)可靠地锁定多个mutex,或者重新发明std::lock之类的算法以避免死锁。一个这样的替代算法是,如果你可以保证每个人总是以相同的顺序(例如索引)锁定mutexes中的互斥锁,那么你根本不需要std::lock,只需循环直播并锁定`EM。

  2. 由于lock_guard要求vector移动可构造,因此
  3. vector<T>::emplace_back一次放入T是有问题的。这是unique_lock在这里工作而lock_guard没有的原因之一。 mutexes因持有不可移动的互斥锁而失控,因为它会立即构建vector所有内容,而不是emplace_back添加{。}}。

  4. 在此示例中,locks包含对mutexes的引用。确保这两个容器之间没有生命周期问题(mutexes必须超过locks)。

  5. 如果您需要在序列末尾添加不可移动的项目,请切换到deque,这将在vector不会的位置使用。

  6. 解锁订单无所谓,不用担心。锁定顺序仅在不同的线程可能锁定不同的顺序时才有意义。如果所有线程始终以相同的顺序锁定,请不要担心。但是如果所有线程总是以相同的顺序锁定,请考虑用单个互斥锁替换n个互斥锁,因为这听起来相当。

  7. 上面的代码假设不同的线程可能以不同的顺序锁定,也许是mutexes的子集。显然它不会扩展到大n

  8. 在问题中使用 Edit2 ,我相信这段代码是可行的。它将可靠地用于以不同顺序锁定mutexes的不同线程。每个帖子应该形成自己的locks本地副本,并通过switch发送。如果一个线程由于某种原因需要其locks成为mutexes的子集,或者以不同的顺序构建它,那么没问题。这就是该解决方案的目的。

    <强>插头

    如果您对std::lock背后的算法感兴趣,可以参考以下各种潜在实施的性能测试,包括可以在您自己的平台上运行的测试代码:

    Dining Philosophers Rebooted

    如果您发现std::lock的实施不是最理想的,请与您的实施者进行讨论。 : - )

答案 1 :(得分:0)

可以使用new构建lock_guards并将它们放入unique_ptr s吗?

然后该向量将保留std::unique_ptr<std::lock_guard<std::mutex>>而不只是std::lock_guard<std::mutex>

std::vector<std::mutex> locks_(n);
std::vector<std::unique_ptr<std::lock_guard<std::mutex>>> guards(n);
for (int i = 0; i < n; i++) {
  typedef std::lock_guard<std::mutex> LockGuardType;
  std::unique_ptr<LockGuardType> guard(new LockGuardType(locks_[i]));
  guards.emplace_back(std::move(guard));
}

那应该编译得很好。