我希望在我的项目中尽可能减少同步并编写无锁代码。当绝对必要时,我喜欢用pthread和win32互斥锁替换原子操作中构建的轻量级自旋锁。我的理解是这些是下面的系统调用,并且可能导致上下文切换(对于非常快速的关键部分可能是不必要的,其中简单地旋转几次将是更好的。)
我所指的原子操作在这里有详细记录:http://gcc.gnu.org/onlinedocs/gcc-4.4.1/gcc/Atomic-Builtins.html
这是一个例子来说明我在说什么。想象一下可能有多个读者和作者的RB树。 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;
}
};
(假设它不必是例外安全的)
这段代码确实是线程安全的吗?这个想法有利有弊吗?有什么建议?如果线程不是真正并发的话,使用像这样的自旋锁是个坏主意吗?
提前致谢。 ;)
答案 0 :(得分:4)
volatile
上需要lock
限定符,我还需要sig_atomic_t
。没有volatile
限定符,此代码:
u16 savedlock = lock;
while (savedlock == 0xffff || __sync_bool_compare_and_swap(&lock, savedlock, savedlock+1) == false)
savedlock = lock;
在while循环体中更新lock
时,可能无法重新读取savedlock
。考虑lock
是0xffff的情况。然后,在检查循环条件之前,savedlock
将为0xffff,因此while
条件将在调用__sync_bool_compare_and_swap
之前短路。由于未调用__sync_bool_compare_and_swap
,因此编译器不会遇到内存障碍,因此可能会合理地假设lock
的值未在您下面发生更改,并避免在savedlock
中重新加载它{1}}。
回复:sig_atomic_t
,有一个不错的讨论here。适用于信号处理程序的相同注意事项也适用于线程。
通过这些更改,我猜你的代码是线程安全的。不过,我仍然会建议使用互斥锁,因为你真的不知道你的RB树插入在一般情况下需要多长时间(根据我之前的评论)。
答案 1 :(得分:1)
值得注意的是,如果您使用的是Win32互斥锁,那么从Vista开始,就会为您提供一个线程池。根据您使用RB树的内容,您可以替换它。
另外,你应该记住的是原子操作并不是特别快。微软称他们每个人都有几百个周期。
不是试图以这种方式“保护”功能,而是简单地同步线程,更改为SIMD /线程池方法或仅使用互斥锁可能更有效。
但是,当然,如果没有看到您的代码,我就无法再发表任何评论了。多线程的麻烦在于你必须看到某个人的整个模型才能理解它。