我有一个大型数组(3e9元素)的数据,我在多个线程中更新它的值。我刚刚发现有竞争条件。
我认为没有必要锁定整个函数,因为元素彼此独立,data[1]
和data[234]
的更新可以安全地同时进行。
我还发现data[]
中每个元素的最重要位将永远不会被使用。在那个位上实现GCC原子内置锁是否安全?
我的代码如下,但似乎正在陷入僵局。
const unsigned short LOCK_MASK = 1<<15;
unsigned short * lock = &(data[position]);
unsigned short oldLock, newLock;
//lock
do {
oldLock = *lock;
newLock = oldLock ^ LOCK_MASK;
} while ((oldLock & LOCK_MASK) || !__sync_bool_compare_and_swap(lock, oldLock, newLock));
//update data[position] here
...
...
...
//unlock
*lock ^= LOCK_MASK;
我还阅读了这篇文章(Lightweight spinlocks built from GCC atomic operations?)并在volatile
data
编辑在我的设计中,0表示已解锁,1表示已锁定
答案 0 :(得分:1)
您的代码包含许多数据争用,包括oldLock = *lock
和解锁位*lock ^= LOCK_MASK
,
由于没有发布障碍,它无法将更新同步到其他核心。
请注意,除了锁定数组段以进行写访问外,还需要锁定该段以进行读访问,因为必须同步读取和写入。
在该位上实现GCC原子内置锁是否安全?
如果要表示用于读取和写入访问的单独状态(未锁定,读取锁定x N,写入锁定),则需要多位。 单个位将锁定限制为2种状态,已锁定和已解锁,根据您的代码,可以通过以下方式实现:
const unsigned short LOCK_MASK = 1<<15;
void lock_array_segment(int position)
{
unsigned short *lock = &data[position]; // global array
unsigned short oldLock, newLock;
do {
oldLock = __atomic_load_n (lock, __ATOMIC_RELAXED);
newLock = oldLock | LOCK_MASK; // set bit
} while ((oldLock & LOCK_MASK) || !__sync_bool_compare_and_swap(lock, oldLock, newLock));
}
void unlock_array_segment(int position)
{
unsigned short *lock = &data[position]; // global array
unsigned short oldLock, newLock;
oldLock = __atomic_load_n (lock, __ATOMIC_RELAXED);
newLock = oldLock & ~LOCK_MASK; // clear bit
__atomic_store_n (lock, newLock, __ATOMIC_RELEASE);
}
__sync_bool_compare_and_swap
的文档说在大多数情况下,这些内置版本被视为完全障碍。你需要一个获取障碍,所以应该涵盖。
由于您的方法基于自旋锁,因此如果您想要长时间保持读锁,则效果不佳。在这种情况下,请考虑一种更简单的方法,为数据阵列中需要锁定的每个段使用单独的互斥锁。
如果您想为多个读者提供访问权限,请考虑使用std::shared_mutex
(C ++ 17)或boost::shared_mutex
。
答案 1 :(得分:0)
您应该考虑更多标准的锁定方式(C++11或更好)。
也许从阅读一些Pthread tutorial开始(至少对于那里解释的概念)。
在C ++中阅读atomic operations和thread support。
您可以启发式地考虑为连续1024(或其他两个幂)元素的每个段使用互斥锁。
您可以考虑使用producer-consumer方法。