我有一个大数组(3E9元素)数据,我正在在多个线程中更新其值。我刚刚发现有种族条件。
我认为锁定整个功能是不必要的,因为元素彼此独立,可以同时安全完成data[1]
和data[234]
上的更新。
我还发现data[]
中每个元素中最重要的位永远不会使用。在该位上实现GCC Atomic内置锁是否安全?
我的代码如下,但似乎正在发生僵局。
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;
我还阅读了这篇文章(由GCC原子操作构建的轻质自旋锁?),并在我的data
volatile
编辑在我的设计中,0表示解锁,1表示锁定
您的代码包含许多数据竞赛,包括oldLock = *lock
并解锁位*lock ^= LOCK_MASK
,由于没有发布障碍,这无法使您的更新与其他内核同步。
请注意,除了锁定数组段以进行写入访问之外,您还需要锁定该段以进行读取访问,因为必须同步读取和写入。
在该位上实现GCC Atomic内置锁是否安全?
如果要表达单独的状态进行读取和写入访问(解锁,锁定的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
的文档说,这些内置被认为是完整的障碍。您需要在这里获得障碍,因此应涵盖。
由于您的方法是基于Spinlocking,因此如果您想将读书保留更长的时间,则无法正常工作。在这种情况下,考虑需要锁定的数据阵列中每个段的单独的segment使用单独的静音方法来考虑更直接的方法。如果要让多个阅读器访问,请考虑使用std::shared_mutex
(C 17)或boost::shared_mutex
。
您应该考虑更多的标准锁定方式(在C 11或更高)。
也许首先要阅读一些pthread教程(至少对于那里解释的概念)。
阅读有关C 中的原子操作和线程支持。
您可以在连续的1024(或两个其他功率)元素的每个段中考虑静音。
您可能会考虑一种生产者 - 消费者的方法。