问题描述
我想在向量或双端队列等容器中存储可变数量的互斥锁.
I would like to store a variable number of mutexes in a container like vector or deque.
在其中一种用例中,我需要可靠且无死锁地锁定所有互斥锁.我还希望获得异常安全保证,如果引发异常,则所有互斥锁都好像没有发生锁定.
In one of the use cases, I need to reliably and deadlock-free lock all of the mutexes. I would also like to have exception safety guarantee, that if an exception is thrown, all of the mutexes are as if no locking occured.
我正在尝试做类似的事情:
I am trying to do something like:
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));
}
但是它没有编译,给了我
but it doesn't compile, giving me:
我猜想在lock_guards被销毁时也可能会出现问题,因为与构造相比,必须颠倒顺序,但是该标准为我们节省了:
I guess there might also be a problem when lock_guards are destroyed, because the order must be reversed as compared to construction, but the standard saves us with:
这种方法有潜在的陷阱吗?如何使它起作用?
Are there any potential pitfalls with this approach and how can one make it work?
实际上我是错的,看来媒介并不能保证特定的销毁顺序.看到以下问题:破坏std :: vector元素的顺序
actually I am wrong, it seems vector does not guarantee a particular order of destruction. See this question: Order of destruction of elements of an std::vector
Q :如果用例是:
所有互斥锁由不同的线程以任何顺序锁定/解锁(但是每个线程一次仅使用1个互斥锁),但是在某些时候,我需要以安全的方式将所有互斥锁锁定在另一个线程中.
All the mutexes are locked/unlocked in any order by different threads (however each of those threads uses only 1 mutex at a time),but at some point I need to lock all of the mutexes in a safe manner in another thread.
推荐答案
在n
上具有固定且较低的上限,您可以合理地执行以下操作:
With a firm and low upper bound on n
you could reasonably do something like this:
#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";
}
}
不是那么漂亮.但是很容易推理,因此很可靠.
It isn't that pretty. But it is easy to reason about and thus reliable.
注意:
-
您需要使用
std::lock(m1, m2, ...)
可靠地锁定多个mutex
,或者重新发明诸如std::lock
的算法以避免死锁.一种这样的替代算法是,如果您可以保证每个人始终以相同的顺序(按索引表示)锁定mutexes
中的互斥锁,那么您根本就不需要std::lock
,只需循环并锁定`em.
You need to use
std::lock(m1, m2, ...)
to reliably lock more than onemutex
, or re-invent an algorithm such asstd::lock
to avoid deadlock. One such alternative algorithm is if you can guarantee that everyone always locks the mutexes inmutexes
in the same order (say by index), then you don't needstd::lock
at all, just loop thru and lock `em.
lock_guard
一次放入vector
是有问题的,因为vector<T>::emplace_back
要求T
是可移动构造的.这就是unique_lock
在这里工作而lock_guard
不能在这里工作的原因之一. mutexes
放弃持有不可移动的互斥锁,因为它一次性构造了vector
,而不是通过emplace_back
对其进行添加.
lock_guard
is problematic to put in vector
one at a time as vector<T>::emplace_back
requires T
to be move constructible. That is one of the reasons why unique_lock
works here and lock_guard
doesn't. mutexes
gets away with holding non-movable mutexes because it constructs the vector
all at once instead of adding to it with emplace_back
.
在此示例中,locks
将引用保存到mutexes
中.确保在这两个容器之间没有生命周期问题(mutexes
必须比locks
长寿).
In this example locks
holds references into mutexes
. Make sure you don't have lifetime issues between these two containers (mutexes
must outlive locks
).
如果您需要在序列末尾添加不可移动的项目,请切换到deque
,这将在vector
无法使用的地方起作用.
If you need to add non-movable items to the end of a sequence, switch to deque
, that will work where vector
won't.
解锁顺序无关紧要,不必担心.仅当不同的线程可能以不同的顺序锁定时,锁定顺序才重要.如果所有线程始终以相同的顺序锁定,则不必担心.但是,如果所有线程始终以相同的顺序锁定,请考虑用单个互斥锁替换n个互斥锁,因为这听起来是等效的.
Unlocking order doesn't matter, don't worry about it. Locking order matters only if different threads might lock in different orders. If all threads always lock in the same order, don't worry about it. But if all threads always lock in the same order, consider replacing the n mutexes with a single mutex, as that sounds equivalent.
上面的代码假设,不同的线程可能以不同的顺序锁定,并且可能是mutexes
的子集.显然,它不会扩展到较大的n
.
The code above assumes that somehow different threads might be locking in a different order, and perhaps a subset of mutexes
. And obviously it won't scale to large n
.
对于问题中的 Edit2 ,我认为这段代码是可行的.它可以可靠地与以不同顺序锁定mutexes
的不同线程一起工作.每个线程应形成自己的locks
本地副本,并通过switch
发送该副本.如果某个线程出于某种原因需要其locks
是mutexes
的子集,或以不同的顺序构建它,则没问题.这就是该解决方案的目的.
With Edit2 in the question, I believe this code to be viable. It will reliably work with different threads locking mutexes
in different orders. Each thread should form its own local copy of locks
and send that through the switch
. If a thread for some reason needs its locks
to be a subset of mutexes
, or to build it in a different order, no problem. That is what this solution is designed for.
插件
如果您对std::lock
背后的算法感兴趣,下面是对它的各种潜在实现的性能测试,包括可以在自己的平台上运行的测试代码:
If you are interested in the algorithm behind std::lock
, here are performance tests for a variety of potential implementations of it, including test code that you can run on your own platform:
如果您发现std::lock
的实现不理想,请与实现者进行交流. :-)
If you find that your implementation of std::lock
is suboptimal, have a talk with your implementor. :-)
这篇关于将互斥体存储在矢量/双端c ++中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!