如何使用 std::vector<std::mutex> 之类的东西?

How can I use something like std::vector<std::mutex>?

提问人:Walter 提问时间:5/9/2013 最后编辑:Mateusz PiotrowskiWalter 更新时间:10/23/2021 访问量:36224

问:

我有大量但可能变化的对象并发写入。我想用互斥锁来保护这种访问。为此,我以为我使用了 ,但这不起作用,因为没有复制或移动构造函数,而需要它。std::vector<std::mutex>std::mutexstd::vector::resize()

这个难题的推荐解决方案是什么?

编辑: 是否所有 C++ 随机访问容器都需要复制或移动构造函数来调整大小?std::d eque 会有帮助吗?

再次编辑

首先,感谢您的所有想法。我对避免混淆和/或将它们移动到对象中的解决方案不感兴趣(我不给出细节/原因)。因此,鉴于我想要可调整的互斥锁数量的问题(当没有互斥锁时保证进行调整),那么似乎有几种解决方案。

1 我可以使用固定数量的互斥体,并使用哈希函数从对象映射到互斥体(如 Captain Oblivous 的回答)。这将导致冲突,但如果互斥数远大于线程数,但仍小于对象数,则冲突数应该很小。

2 我可以定义一个包装类(如ComicSansMS的答案),例如

struct mutex_wrapper : std::mutex
{
  mutex_wrapper() = default;
  mutex_wrapper(mutex_wrapper const&) noexcept : std::mutex() {}
  bool operator==(mutex_wrapper const&other) noexcept { return this==&other; }
};

并使用 .std::vector<mutex_wrapper>

3 我可以用来管理单个互斥锁(如 Matthias 的回答)。这种方法的问题在于,每个互斥锁都是在堆上单独分配和取消分配的。因此,我更喜欢std::unique_ptr<std::mutex>

4 std::unique_ptr<std::mutex[]> mutices( new std::mutex[n_mutex] );

当最初分配一定数量的互补时。如果后来发现这个数字不够,我只是n_mutex

if(need_mutex > n_mutex) {
  mutices.reset( new std::mutex[need_mutex] );
  n_mutex = need_mutex;
}

那么我应该使用其中的哪一个(1,2,4)呢?

C++ 多线程 C++11 向量 互斥锁

评论

0赞 Kerrek SB 5/9/2013
vector只需要对象是可移动的,并且它们需要是不可投掷移动的。resize
0赞 Walter 5/9/2013
@KerrekSB除了默认构造函数之外别无他物。std::mutex
3赞 Jerry Coffin 5/10/2013
为了保护前者,您是否可以使用 ?collection<T>collection<mutex>collection<std::atomic<T>>
2赞 BTownTKD 5/10/2013
我觉得可能有比你想做的更好的方法。您能否提供一些关于首先需要互斥锁的潜在问题的背景信息?你是如何写入对象的,你打算如何使用互斥锁?顺便说一句,你可以通过添加一个互斥锁作为类的成员字段来使你的对象线程安全。
0赞 Walter 5/10/2013
@BTownTKD抱歉,但这会有点长......所以我不透露细节。但是,我很确定使用互斥锁的解决方案是最好的解决方案。从表面上看,原子不是一个好主意,因为这将由库使用更细粒度的互斥锁来实现。

答:

18赞 Matthias Benkard 5/9/2013 #1

您可以使用代替 .s 是可移动的。std::unique_ptr<std::mutex>std::mutexunique_ptr

评论

2赞 Walter 5/9/2013
并在堆上单独创建每个互斥锁?开什么玩笑?
10赞 Mike Seymour 5/10/2013
@Walter:这是一个非常明智的建议,如果额外的内存占用和额外的间接级别的成本是可以接受的。
1赞 Walter 5/10/2013
@MikeSeymour以及分配和取消分配许多单个互斥锁的成本。好吧,这是一个可怕的解决方法。真的,我根本不想复制互斥锁。我想要的只是一个我可以更改的数字中的默认构造互斥锁。不幸的是,vector::resize() 不能做到这一点......
2赞 GManNickG 5/10/2013
@Walter:为什么不创建一个类来保持互斥锁与其对象相关联?然后编写自定义复制和移动构造函数以及赋值运算符,使你的类可以在容器中很好地使用。保持裸露的互斥锁似乎有点愚蠢。
1赞 Matthias Benkard 5/11/2013
@Walter 在这种情况下,您需要在调整大小时重新创建所有互斥锁。只有当当前没有现有的互斥锁时,才能安全地执行此操作。你能保证吗?
23赞 Mike Seymour 5/10/2013 #2

vector要求值是可移动的,以便在值增长时保持连续的值数组。您可以创建一个包含互斥锁的向量,但您不能执行任何可能需要调整其大小的操作。

其他容器没有该要求;要么 or 应该工作,只要您在构造过程中就地构造互斥锁,或者使用 或 .诸如 和 之类的函数将不起作用。deque[forward_]listemplace()resize()insert()push_back()

或者,您可以添加额外的间接和存储级别;但是您在另一个答案中的评论表明您认为动态分配的额外成本是不可接受的。unique_ptr

评论

1赞 Walter 5/10/2013
怎么样?在构造时,向量将使用其元素的默认构造函数,所以没有问题。std::unique_ptr<std::vector<std::mutex>>
0赞 Walter 5/10/2013
我也想过.为什么我需要使用 ?(deque)的构造函数不会简单地使用其元素的默认构造函数吗?std::dequeemplace()
0赞 Mike Seymour 5/10/2013
@Walter:确实,使用构造函数是可以的。我的意思是你必须使用而不是添加单独的互斥锁,但我说得不够清楚。emplace()push_back()
2赞 Mike Seymour 5/10/2013
@Walter:如果要调整矢量的大小,则不能使用矢量;将其包装在 A 中不会有任何区别。如果你有一个固定的数字,那么一个适当构造的向量应该有效;但是你说“可能有所不同”,这排除了使用 .unique_ptrvector
1赞 Mike Seymour 5/10/2013
@Walter:好的,我现在明白你的意思了。这应该有效,但请确保在任何互斥锁被锁定时永远不要这样做。
9赞 Captain Obvlious 5/10/2013 #3

我建议使用固定互斥池。保留一个固定的数组,并根据对象的地址选择要锁定的数组,就像使用哈希表一样。std::mutex

std::array<std::mutex, 32> mutexes;

std::mutex &m = mutexes[hashof(objectPtr) % mutexes.size()];

m.lock();

该函数可以是一些简单的函数,它将指针值移动几位。这样,您只需初始化一次互斥锁,即可避免调整向量大小的副本。hashof

评论

0赞 Walter 5/10/2013
有趣的想法。不能保证没有两个 objectPtr 导致相同的互斥锁,从而导致更多争用。我认为只要互斥锁的数量大于线程的数量(多少),这就可以了。
0赞 Captain Obvlious 5/10/2013
没错,你会遇到碰撞。如果这些碰撞对您没有影响,这可能是一个更有效的替代方案。
0赞 Walter 5/10/2013
我已经使用了比实际 ocbject 少得多的互斥锁,并将对象分组为块,因为每当线程必须写入一个对象时,它很可能也会写入附近的对象。
0赞 Antonio 2/27/2023
这是一个绝妙的主意!它适用于我的情况,其中冲突的影响最小(只需稍等片刻,而大向量中包含的智能指针要么被复制、更新,要么被重置)
3赞 schoppenhauer 5/10/2013 #4

如果效率是一个问题,我假设你只有非常小的数据结构,这些结构经常变化。因此,最好使用原子比较和交换(以及其他原子操作)而不是使用互斥锁,特别是std::atomic_compare_exchange_strong

21赞 Awwit 6/12/2014 #5

如果要创建特定长度:

std::vector<std::mutex> mutexes;
...
size_t count = 4;
std::vector<std::mutex> list(count);

mutexes.swap(list);

评论

3赞 Aaron McDaid 8/20/2015
这个答案证实了母鹿不需要可移动的物品。这取决于您如何处理 .你可以以一种方式增长一个(只有一种方式,你的技巧),但你可以很容易地缩小这样一个向量,你可以(如你的答案一样)确保它一开始就有正确的大小。vectorvectorvector<mutex>swap
0赞 Supamee 3/27/2017
我不明白为什么这个答案不是首选?除了更大的开销之外,这在功能上与 .resize() 有何不同。你说它只能以一种方式增长是什么意思?(对不起,如果这不是正确的格式,我是论坛的新手)
4赞 Supamee 3/28/2017
在搞砸了它之后,我现在明白了为什么;如果使用 .swap() 来增长向量,则所有元素都是新的。
0赞 lvella 10/12/2020
我想知道这是否是一个实现怪癖,或者这应该适用于每个符合标准的 C++ 库。
-4赞 cyw 11/29/2018 #6

将每个互斥锁声明为指针怎么样?

std::vector<std::mutex *> my_mutexes(10)
//Initialize mutexes
for(int i=0;i<10;++i) my_mutexes[i] = new std::mutex();

//Release mutexes
for(int i=0;i<10;++i) delete my_mutexes[i];
1赞 Matthew M. 10/23/2021 #7

当我想要一个 es 或 s 时,我有时会按照您的第二个选项使用解决方案,每个选项都有自己的 .当然,这有点乏味,因为我编写了自己的复制/移动/赋值运算符。std::vectorclassstructstd::mutex

struct MyStruct {
  MyStruct() : value1(0), value2(0) {}
  MyStruct(const MyStruct& other) {
    std::lock_guard<std::mutex> l(other.mutex);
    value1 = other.value1;
    value2 = other.value2;
  }
  MyStruct(MyStruct&& other) {
    std::lock_guard<std::mutex> l(other.mutex);
    value1 = std::exchange(other.value1, 0);
    value2 = std::exchange(other.value2, 0);
  }
  MyStruct& operator=(MyStruct&& other) {
    std::lock_guard<std::mutex> l1(this->mutex), l2(other.mutex);
    std::swap(value1, other.value1);
    std::swap(value2, other.value2);
    return *this;
  }
  MyStruct& operator=(const MyStruct& other) {
    // you get the idea
  }
  int value1;
  double value2;
  mutable std::mutex mutex;
};

您不需要“移动”.你只需要在“移动”其他所有东西时按住它。std::mutex