本文介绍了将互斥体存储在矢量/双端c ++中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在向量或双端队列等容器中存储可变数量的互斥锁.

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.

注意:

  1. 您需要使用std::lock(m1, m2, ...)可靠地锁定多个mutex,或者重新发明诸如std::lock的算法以避免死锁.一种这样的替代算法是,如果您可以保证每个人始终以相同的顺序(按索引表示)锁定mutexes中的互斥锁,那么您根本就不需要std::lock,只需循环并锁定`em.

  1. You need to use std::lock(m1, m2, ...) to reliably lock more than one mutex, or re-invent an algorithm such as std::lock to avoid deadlock. One such alternative algorithm is if you can guarantee that everyone always locks the mutexes in mutexes in the same order (say by index), then you don't need std::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发送该副本.如果某个线程出于某种原因需要其locksmutexes的子集,或以不同的顺序构建它,则没问题.这就是该解决方案的目的.

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 ++中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-14 19:21