共享互斥锁是否比相对较大结构的原子互斥锁更有效?
Is a shared mutex more efficient than an atomic of a relatively big struct?
假设我有一个看起来像这样的类(实际上就是这个大小):
class K
{
public:
long long get_x() const; // lock m_mutex in shared/read-only mode
void update( long long w ); // lock m_mutex with a unique_lock
private:
long long m_a;
long long m_b;
long long m_c;
long long m_x;
double m_flow_factor;
mutable boost::shared_mutex m_mutex;
};
如您所见,这应该是线程安全的。update函数每次由一个线程调用,未知但只有一个线程(保证),但访问器可以由多个线程同时调用。
update函数正在改变所有的值,并且经常被调用(每秒100次)。正如你所猜到的,当前的实现将会锁定很多。
我正在考虑使用std::atomic来避免锁,并可能使这段代码更高效。但是,我确实需要更新函数来一起更新这些值。因此,我正在考虑这样做:
class K
{
public:
long long get_x() const
{ return data.load().x; }
void update( long long w )
{
auto data_now = data.load();
// ... work with data_now
data.store( data_now );
}
private:
struct Data {
long long a;
long long b;
long long c;
long long x;
double flow_factor;
};
std::atomic<Data> data;
};
我目前对std::atomic的理解是,即使这段代码比前面的代码更可读(因为它没有到处都有锁声明),作为K::Data结构体是"大的",std::atomic将只使用一个普通的互斥锁来实现(所以它不应该比我最初的实现更快)。
我说的对吗?
对于这样一个结构体的std:atomic的任何专门化都将涉及到内部锁定,因此您将一无所获,而且现在您还将面临以前没有的加载和存储之间的数据竞争,因为在以前的版本中,整个块都有独占锁定(我猜?)。
对于shared_mutex,使用普通互斥锁与shared_mutex进行分析可能是明智的,您可能会发现普通互斥锁的性能更好(这取决于您持有锁的时间)。
shared_mutex的好处只有在锁被持有很长一段时间以供读取并且写操作很少的情况下才能看到,否则,shared_mutex所涉及的开销会抵消普通互斥锁所带来的任何收益。
std::atomic并不一定比std::mutex慢。例如,在MSVC 14.0中,std::atomic的实现。Store看起来像这样:
inline void _Atomic_copy(
volatile _Atomic_flag_t *_Flag, size_t _Size,
volatile void *_Tgt, volatile const void *_Src,
memory_order _Order)
{ /* atomically copy *_Src to *_Tgt with memory ordering */
_Lock_spin_lock(_Flag);
_CSTD memcpy((void *)_Tgt, (void *)_Src, _Size);
_Unlock_spin_lock(_Flag);
}
inline void _Lock_spin_lock(
volatile _Atomic_flag_t *_Flag)
{ /* spin until _Flag successfully set */
while (_ATOMIC_FLAG_TEST_AND_SET(_Flag, memory_order_acquire))
_YIELD_PROCESSOR;
}
不能保证自旋锁会比合适的std::互斥锁更快。这取决于你到底在做什么。但是与std::mutex相比,std::atomic并不总是次优的解决方案。
无论是否使用锁实现,原子(即顺序一致性)的默认使用都相当慢,可能与互斥锁一样慢。调整update()
中的内存屏障可能会带来更好的性能:
void update( long long w )
{
auto data_now = data.load(std::memory_order_acquire);
// ... work with data_now
data.store(data_now, std::memory_order_release);
}
同样,如果您不需要线程之间的顺序一致性(您不同步写入器和读取器),您可以进一步优化读取器:
long long get_x() const
{ return data.load(std::memory_order_relaxed).x; }
- 如果变量名称不跟在 char* 后面,const char* 是否有效?
- 钳制迭代器是否有效
- 检查由括号、方括号和大括号组成的一组方括号是否有效?
- 在函数内创建的对象的范围 - 如果在函数外部存储和访问引用,它们是否有效?
- 模板签名解析为 void(void) 被 GCC 拒绝;这是否有效C++?
- 我如何知道作为参数的size_t在函数中是否有效?
- 是否可以在C++中获取 CHAR 的有效十六进制地址?
- 我的运算符重载是否有效<<(流插入)左操作数不是 ostream
- C++ 返回指向函数内定义的静态数组的指针是否有效?
- 模板变量是否允许在多个翻译单元中并有效合并?
- 错误:在尝试检测 std::cout 是否<< t 时,功能强制转换为数组类型;有效
- 声明后,gcc 的动态大小数组是否与标准数组有效相同?
- 此递归模板类型定义是否有效C++?
- 将 C 函数转换为 C++ 以检查数字是否有效
- 函数参数的名称与调用函数时使用的变量相同是否有效?
- 如何检查输入是否有效?
- 如何检查用户的输入是否有效以及我正在寻找的数字?
- 堆分配对于大型块中的分页是否更有效?
- 在函数中按值传递 unordered_map/unordered_set 是否有效? C++
- 如何检查isupper(cstr)是否有效?