【发布时间】:2011-02-12 16:08:32
【问题描述】:
我想在我的项目中尽可能减少同步并编写无锁代码。当绝对必要时,我很想用原子操作构建的轻量级自旋锁代替 pthread 和 win32 互斥锁。我的理解是,这些是底层的系统调用,可能会导致上下文切换(这对于非常快速的关键部分可能是不必要的,只需旋转几次就可以了)。
我所指的原子操作在此处有详细记录:http://gcc.gnu.org/onlinedocs/gcc-4.4.1/gcc/Atomic-Builtins.html
这里有一个例子来说明我在说什么。想象一个可能有多个读取器和写入器的 RB-tree。 RBTree::exists() 是只读且线程安全的,RBTree::insert() 需要单个写入者(并且没有读取者)的独占访问才能安全。一些代码:
class IntSetTest
{
private:
unsigned short lock;
RBTree<int>* myset;
public:
// ...
void add_number(int n)
{
// Aquire once locked==false (atomic)
while (__sync_bool_compare_and_swap(&lock, 0, 0xffff) == false);
// Perform a thread-unsafe operation on the set
myset->insert(n);
// Unlock (atomic)
__sync_bool_compare_and_swap(&lock, 0xffff, 0);
}
bool check_number(int n)
{
// Increment once the lock is below 0xffff
u16 savedlock = lock;
while (savedlock == 0xffff || __sync_bool_compare_and_swap(&lock, savedlock, savedlock+1) == false)
savedlock = lock;
// Perform read-only operation
bool exists = tree->exists(n);
// Decrement
savedlock = lock;
while (__sync_bool_compare_and_swap(&lock, savedlock, savedlock-1) == false)
savedlock = lock;
return exists;
}
};
(假设它不需要是异常安全的)
这段代码真的是线程安全的吗?这个想法有什么优点/缺点吗?有什么建议吗?如果线程不是真正并发的,那么使用这样的自旋锁是不是一个坏主意?
提前致谢。 ;)
【问题讨论】:
-
我在类似问题stackoverflow.com/questions/1919135/… 中给出的答案可能与此处相关。
-
您的回答肯定与一般使用自旋锁的问题有关。在典型情况下,它们对于 smp 机器来说似乎是个好主意。最坏的情况(在关键部分停止运行的写入器)是否会与两个并发线程同时尝试插入的更可能的情况相同?在混合线程环境中,用户线程被映射到与机器上的逻辑处理器数量相等的内核线程数上呢?到那时,最坏的情况就更不可能了;没有?
-
我不确定内核线程的数量在多大程度上影响了遇到性能问题的可能性。有可能编写器线程刚刚用完了锁进入和退出之间的时间片,这将导致无论有多少内核线程都出现问题。在这一点上,我会注意到 RB-tree 的插入操作是 O(log(n)),所以树越大,这个问题发生的可能性就越大。此外,较大的树更有可能在更新期间导致页面错误,这也会使问题案例更有可能发生。我会在这里避免使用自旋锁。
-
正如我在 DeadMG 的回答中提到的,“轻量级”方面可能很重要。毫无疑问,互斥体的开销比 16 位值和一些原子操作要多得多。在“旋转”中添加 sched_yield() 或类似的调用会改善您的估计吗?
标签: c++ gcc multithreading pthreads thread-safety