我可以使用我的部分数据作为锁吗?
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 种状态,locked 和 unlocked,根据您的代码,可以通过以下方式实现:
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 方法。
我有一个大型数据数组(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 种状态,locked 和 unlocked,根据您的代码,可以通过以下方式实现:
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 方法。