我想在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)。
这种方法是否存在任何潜在的缺陷?如何才能使其发挥作用?
问:如果用例是:
,该怎么办?所有互斥锁都由不同的线程以任何顺序锁定/解锁(但是每个线程一次只使用1个互斥锁), 但在某些时候,我需要在另一个线程中以安全的方式锁定所有互斥锁。
答案 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";
}
}
不是那么漂亮。但它很容易推理并因此可靠。
注意:
您需要使用std::lock(m1, m2, ...)
可靠地锁定多个mutex
,或者重新发明std::lock
之类的算法以避免死锁。一个这样的替代算法是,如果你可以保证每个人总是以相同的顺序(例如索引)锁定mutexes
中的互斥锁,那么你根本不需要std::lock
,只需循环直播并锁定`EM。
lock_guard
要求vector
移动可构造,因此 vector<T>::emplace_back
一次放入T
是有问题的。这是unique_lock
在这里工作而lock_guard
没有的原因之一。 mutexes
因持有不可移动的互斥锁而失控,因为它会立即构建vector
所有内容,而不是emplace_back
添加{。}}。
在此示例中,locks
包含对mutexes
的引用。确保这两个容器之间没有生命周期问题(mutexes
必须超过locks
)。
如果您需要在序列末尾添加不可移动的项目,请切换到deque
,这将在vector
不会的位置使用。
解锁订单无所谓,不用担心。锁定顺序仅在不同的线程可能锁定不同的顺序时才有意义。如果所有线程始终以相同的顺序锁定,请不要担心。但是如果所有线程总是以相同的顺序锁定,请考虑用单个互斥锁替换n个互斥锁,因为这听起来相当。
上面的代码假设不同的线程可能以不同的顺序锁定,也许是mutexes
的子集。显然它不会扩展到大n
。
在问题中使用 Edit2 ,我相信这段代码是可行的。它将可靠地用于以不同顺序锁定mutexes
的不同线程。每个帖子应该形成自己的locks
本地副本,并通过switch
发送。如果一个线程由于某种原因需要其locks
成为mutexes
的子集,或者以不同的顺序构建它,那么没问题。这就是该解决方案的目的。
<强>插头强>
如果您对std::lock
背后的算法感兴趣,可以参考以下各种潜在实施的性能测试,包括可以在您自己的平台上运行的测试代码:
如果您发现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));
}
那应该编译得很好。