多个变量的多个互斥量

Multiple mutexes for multiple variables

基于

我对线程之间共享的不同变量进行了一些冗长的赋值。将这些赋值拆分到单个范围内以防止单个 lock_guard 占用所有变量是否有意义?

如果前一个线程移动到第二个范围,则下一个线程可以使用第一个变量。

我的猜测是这取决于被调用函数的复杂程度。如果你只有一个赋值,它肯定比第二个 lock_guard locking/unlocking 另一个互斥体更快。

伪代码

// all used variables beside the lock_guards are created and initialized somewhere else

// do something ...

{
    std::lock_guard<std::mutex> lock(mut0);
    shared_var0 = SomeFunctionTakingSameTime0();  
}

{
    std::lock_guard<std::mutex> lock(mut1);
    shared_var1 = SomeFunctionTakingSameTime1();  
}

// do this again or other stuff ...

这种代码结构的优点或缺点是什么?

根据您的描述,我认为 SomeFunctionTakingSameTime0() 是一个比较耗时的函数。但是与共享变量交互的唯一方式是赋值。因此,这样做可能会更好:

auto temp = SomeFunctionTakingSameTime0();
{
    std::lock_guard<std::mutex> lock(mut0);
    shared_var0 = std::move(temp);  
}

由于您为锁提供了两个不同的互斥量 mut0mut1,您最好的办法是分别获取它们。

Does it make sense to split those assignments in single scopes to prevent a single lock_guard to occupy all variables?

没错。的确,你的直觉是正确的。

What may be advantages or disadvantages for this type of code structure?

但是,这种情况无法用非常有限的代码片段来预测。这里更好的建议是执行一些基准测试。

我个人会考虑的一些重要方面:

  • 类型 shared_var0?
  • 锁定互斥体的时间 > 完成时间 SomeFunctionTakingSameTime1?

正如您所说,如果您的函数 SomeFunctionTakingSameTime0SomeFunctionTakingSameTime1 将花费 相当长的时间 ,那么分成两个不同的范围可能有助于最大化吞吐量。

一般来说,临界区(即你持有锁的范围)应该越短越好。

当然,就像所有事情一样,需要权衡取舍。 lock/unlock 操作不是免费的。

int shared; 
lock(mutex);
shared = 1;
unlock(mutex);

lock(mutex);
shared = 2;
unlock(mutex);

当然,这是一个愚蠢的例子。但它显示了锁定获取操作的关键方面。 示例中的赋值操作比锁定互斥量两次要便宜得多! 此外,它会阻止一些编译器优化(赋值 shared = 1 可以完全删除 - 从语义的角度来看 -)。

总而言之,如果 shared_var0shared_var1 的类型是基本类型(intfloat 等),您甚至可以考虑将它们存储到std::atomic 并完全避免互斥。