【问题标题】:Can I use part of my data as lock?我可以使用我的部分数据作为锁吗?
【发布时间】:2017-02-20 03:25:56
【问题描述】:

我有一个大型数据数组(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?) 并在我的data 上添加了volatile

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

【问题讨论】:

  • 获取锁之前的所有读取都需要同步。特别是,oldLock = *lock; 是错误的,它需要是原子的。就目前而言,如果(oldLock &amp; LOCK_MASK) 曾经为真,优化器可能会假设*lock 永远不会改变。
  • 一种更保守的方法可能是分配适当数量的真正互斥锁(或自旋锁或您想使用的任何机制)并使用它们来同步对数据的访问。例如如果你有一个 M 互斥体数组,那么在读取或写入数据数组中的值 #i 之前,只需让每个线程锁定互斥体 #(i%M)。调整 M 的值,直到找到它的最小值,其中互斥体的争用仍然可以接受。

标签: c++ multithreading gcc compare-and-swap


【解决方案1】:

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

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

在该位上实现 GCC 原子内置锁是否安全?

如果您想表达读写访问的不同状态(解锁、读锁定 x N、写锁定),则需要多个位。 单个位将锁定限制为 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 的文档说在大多数情况下,这些内置函数被认为是一个完整的屏障。您需要在这里设置一个获取障碍,因此应该将其覆盖。

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

【讨论】:

  • 以前不知道 _atomic* 指令!谢谢
【解决方案2】:

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

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

阅读 C++ 中的 atomic operationsthread support

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

您可以考虑使用producer-consumer 方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-13
    • 2022-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-03
    • 2013-05-23
    • 2016-04-29
    相关资源
    最近更新 更多