从GCC原子操作构建的轻量级自旋锁?

时间:2010-04-27 01:18:21

标签: c++ gcc multithreading pthreads thread-safety

我希望在我的项目中尽可能减少同步并编写无锁代码。当绝对必要时,我喜欢用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;
    }
};

(假设它不必是例外安全的)

这段代码确实是线程安全的吗?这个想法有利有弊吗?有什么建议?如果线程不是真正并发的话,使用像这样的自旋锁是个坏主意吗?

提前致谢。 ;)

2 个答案:

答案 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 /线程池方法或仅使用互斥锁可能更有效。

但是,当然,如果没有看到您的代码,我就无法再发表任何评论了。多线程的麻烦在于你必须看到某个人的整个模型才能理解它。