我可以使用我的部分数据作为锁吗?

Can I use part of my data as lock?

我有一个大型数据数组(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; 

我也读过这个 post (Lightweight spinlocks built from GCC atomic operations?) 并在我的 data

上添加了 volatile

EDIT 在我的设计中,0表示解锁,1表示锁定

您的代码包含大量数据竞争,包括 oldLock = *lock 和解锁位 *lock ^= LOCK_MASK, 由于没有发布障碍,无法将您的更新同步到其他内核。

请注意,除了锁定数组段以进行写访问外,您还需要锁定该段以进行读访问,因为读写必须同步。

Is it safe to implement a GCC atomic builtin lock on that bit?

如果要表示读写访问的独立状态(未锁定,read-locked x N,write-locked),则需要多个位。 一位将锁定限制为 2 种状态,lockedunlocked,根据您的代码,可以通过以下方式实现:

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 的文档说 在大多数情况下,这些内置函数被认为是一个完整的障碍。你在这里需要一个获取障碍,所以它应该被覆盖。

由于您的方法基于自旋锁,因此如果您想将 read-lock 保留更长时间,则效果不佳。在这种情况下,请考虑一种更直接的方法,为数据数组中需要锁定的每个段使用单独的互斥体。 如果您想授予多个读者访问权限,请考虑使用 std::shared_mutex (C++17) 或 boost::shared_mutex

您应该考虑更标准的锁定方式(C++11 或更好)。

也许先阅读一些 Pthread tutorial(至少对于那里解释的概念)。

阅读 C++ 中的 atomic operations and thread support

您可以试探性地考虑为连续的 1024(或其他一些二次方)元素的每个段设置一个互斥体。

您可以考虑 producer-consumer 方法。