38

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

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

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

再次编辑

首先,感谢您的所有想法。我对避免静音和/或将它们移动到对象中的解决方案不感兴趣(我不提供细节/原因)。因此,考虑到我想要一个可调整数量的互斥量(当没有互斥量被锁定时保证发生调整)的问题,那么似乎有几种解决方案。

1我可以使用固定数量的 mutice 并使用散列函数从对象映射到 mutice(如 Oblivous 船长的回答)。这将导致冲突,但是如果 mutice 的数量远大于线程的数量,但仍小于对象的数量,则冲突的数量应该很小。

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

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

当最初分配了一定数量n_mutex的mutice时。如果以后发现这个数字不够,我干脆

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

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

4

7 回答 7

19

vector要求这些值是可移动的,以便在其增长时保持一个连续的值数组。您可以创建一个包含互斥体的向量,但您无法做任何可能需要调整其大小的事情。

其他容器没有这个要求;只要在构造过程中或使用or构造互斥体,要么 要么deque应该工作。和将不起作用的功能。[forward_]listemplace()resize()insert()push_back()

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

于 2013-05-09T16:05:09.580 回答
18

如果要创建一定的长度:

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

mutexes.swap(list);
于 2014-06-11T18:35:18.300 回答
17

您可以使用std::unique_ptr<std::mutex>而不是std::mutex. unique_ptrs 是可移动的。

于 2013-05-09T15:45:32.477 回答
7

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

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

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

m.lock();

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

于 2013-05-09T16:30:55.810 回答
3

如果效率是这样一个问题,我假设您只有非常小的数据结构,并且经常更改。那么使用原子比较和交换(和其他原子操作)而不是使用互斥锁可能会更好,特别是std::atomic_compare_exchange_strong

于 2013-05-10T10:47:35.490 回答
0

当我想要每个都有自己std::vectorclasses 或s 时,我有时会使用您的第二个选项的解决方案。当然,当我编写自己的复制/移动/赋值运算符时,这有点乏味。structstd::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. 您只需要在“移动”其他所有内容时锁定它。

于 2021-10-22T18:36:00.397 回答
-4

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

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];
于 2018-11-29T02:02:21.883 回答