C++11使用header实现Spinlock `< atomic>`

oyxsuwqo  于 12个月前  发布在  其他
关注(0)|答案(3)|浏览(90)

我实现了SpinLock类,如下所示

struct Node {
    int number;
    std::atomic_bool latch;

    void add() {
        lock();
        number++;
        unlock();
    }
    void lock() {
        bool unlatched = false;
        while(!latch.compare_exchange_weak(unlatched, true, std::memory_order_acquire));
    }
    void unlock() {
        latch.store(false , std::memory_order_release);
    }
};

字符串
我实现了上面的类,并创建了两个线程,每个线程调用Node类的同一个示例的add()方法1000万次。
很不幸,结果不是两千万,我错过了什么

jutyujz0

jutyujz01#

问题是compare_exchange_weakunlatched变量失败后会更新它。从compare_exchange_weak的文档中可以看到:
将原子对象的包含值的内容与预期值进行比较:-如果为true,则将包含值替换为val(类似于store)。-如果为false,则将预期值替换为包含值。
也就是说,在第一个失败的compare_exchange_weak之后,unlatched将被更新为true,所以下一个循环迭代将尝试compare_exchange_weaktruetrue。这成功了,你只是得到了另一个线程持有的锁。
解决方案:确保在每个compare_exchange_weak之前将unlatched设置回false,例如:

while(!latch.compare_exchange_weak(unlatched, true, std::memory_order_acquire)) {
    unlatched = false;
}

字符串

hpcdzsge

hpcdzsge2#

正如@gexicide所提到的,问题是compare_exchange函数用原子变量的当前值更新expected变量。这也是为什么你必须首先使用局部变量unlatched的原因。为了解决这个问题,你可以在每次循环迭代中将unlatched设置回false。
然而,与其使用compare_exchange来处理它的接口不太适合的事情,不如使用std::atomic_flag来处理:

class SpinLock {
    std::atomic_flag locked = ATOMIC_FLAG_INIT ;
public:
    void lock() {
        while (locked.test_and_set(std::memory_order_acquire)) { ; }
    }
    void unlock() {
        locked.clear(std::memory_order_release);
    }
};

字符串
来源:cppreference
手动指定内存顺序只是一个小的潜在性能调整,我从源代码中复制了这一点。如果简单性比最后一点性能更重要,您可以坚持使用默认值,只需调用locked.test_and_set() / locked.clear()
顺便说一句:std::atomic_flag是唯一保证无锁的类型,尽管我不知道任何平台,在std::atomic_bool上的操作不是无锁的。

**更新:**正如@大卫施瓦茨,@安东和@Technik Empire的评论中所解释的那样,空循环有一些不良的影响,如分支错误预测,HT处理器上的线程饥饿和过高的功耗-所以简而言之,这是一种非常低效的等待方式。影响和解决方案是架构,平台和应用程序特定的。我不是Maven,但通常的解决方案似乎是在Linux上添加cpu_relax()或在Windows上添加YieldProcessor()到循环体。

    • 这里介绍的可移植版本(没有特殊的cpu_relax等指令)对于许多应用程序来说已经足够好了。如果你的SpinLock因为其他人长时间持有锁而旋转很多(这可能已经表明了一个一般的设计问题),那么无论如何使用普通的互斥锁可能更好。
1cklez4t

1cklez4t3#

内存优化只在本地工作,因此,这些内存管理方法(td::memory_order_acquire和std::memory_order_release)是无用的。

相关问题