多个变量的多个互斥量
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);
}
由于您为锁提供了两个不同的互斥量 mut0
和 mut1
,您最好的办法是分别获取它们。
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
?
正如您所说,如果您的函数 SomeFunctionTakingSameTime0
和 SomeFunctionTakingSameTime1
将花费 相当长的时间 ,那么分成两个不同的范围可能有助于最大化吞吐量。
一般来说,临界区(即你持有锁的范围)应该越短越好。
当然,就像所有事情一样,需要权衡取舍。 lock
/unlock
操作不是免费的。
int shared;
lock(mutex);
shared = 1;
unlock(mutex);
lock(mutex);
shared = 2;
unlock(mutex);
当然,这是一个愚蠢的例子。但它显示了锁定获取操作的关键方面。
示例中的赋值操作比锁定互斥量两次要便宜得多!
此外,它会阻止一些编译器优化(赋值 shared = 1
可以完全删除 - 从语义的角度来看 -)。
总而言之,如果 shared_var0
和 shared_var1
的类型是基本类型(int
、float
等),您甚至可以考虑将它们存储到std::atomic
并完全避免互斥。
基于
我对线程之间共享的不同变量进行了一些冗长的赋值。将这些赋值拆分到单个范围内以防止单个 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);
}
由于您为锁提供了两个不同的互斥量 mut0
和 mut1
,您最好的办法是分别获取它们。
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
?
正如您所说,如果您的函数 SomeFunctionTakingSameTime0
和 SomeFunctionTakingSameTime1
将花费 相当长的时间 ,那么分成两个不同的范围可能有助于最大化吞吐量。
一般来说,临界区(即你持有锁的范围)应该越短越好。
当然,就像所有事情一样,需要权衡取舍。 lock
/unlock
操作不是免费的。
int shared;
lock(mutex);
shared = 1;
unlock(mutex);
lock(mutex);
shared = 2;
unlock(mutex);
当然,这是一个愚蠢的例子。但它显示了锁定获取操作的关键方面。
示例中的赋值操作比锁定互斥量两次要便宜得多!
此外,它会阻止一些编译器优化(赋值 shared = 1
可以完全删除 - 从语义的角度来看 -)。
总而言之,如果 shared_var0
和 shared_var1
的类型是基本类型(int
、float
等),您甚至可以考虑将它们存储到std::atomic
并完全避免互斥。