首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用<atomic>实现自旋锁的C++11实现

用<atomic>实现自旋锁的C++11实现
EN

Stack Overflow用户
提问于 2014-10-27 16:20:28
回答 2查看 44.2K关注 0票数 39

我实现了SpinLock类,如下所示

代码语言:javascript
复制
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万次。

不幸的是,结果不是2000万。这里我漏掉了什么?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-10-27 16:24:55

问题是一旦失败,compare_exchange_weak就会更新unlatched变量。来自compare_exchange_weak的文档

将原子对象包含的值的内容与期望的值进行比较:-如果为真,它将包含的值替换为val (如store)。-如果为false,则将expected替换为包含的值。

也就是说,在第一次失败的compare_exchange_weak之后,unlatched将更新为true,因此下一次循环迭代将尝试使用true compare_exchange_weak true。这成功了,并且您刚刚获得了一个由另一个线程持有的锁。

解决方案:确保在每个compare_exchange_weak之前将unlatched设置回false,例如:

代码语言:javascript
复制
while(!latch.compare_exchange_weak(unlatched, true, std::memory_order_acquire)) {
    unlatched = false;
}
票数 48
EN

Stack Overflow用户

发布于 2015-03-22 22:08:39

正如@gexicide所提到的,问题在于compare_exchange函数使用原子变量的当前值更新expected变量。这也是为什么必须首先使用局部变量unlatched的原因。要解决这个问题,可以在每次循环迭代中将unlatched设置回false。

但是,使用std::atomic_flag要简单得多,而不是使用compare_exchange来处理它的接口不太适合的事情:

代码语言:javascript
复制
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上的操作不是无锁的。

更新:正如@David Schwartz,@Anton和@Technik Empire在评论中所解释的那样,空循环有一些不受欢迎的影响,比如分支错误,HT处理器上的线程饥饿和过高的功耗-所以简而言之,这是一种非常低效的等待方式。影响和解决方案取决于体系结构、平台和应用程序。我不是专家,但通常的解决方案似乎是将linux上的cpu_relax()或windows上的YieldProcessor()添加到循环体中。

EDIT2:只是想说清楚:这里介绍的可移植版本(没有特殊的cpu_relax等指令)应该已经足够好用于许多应用程序了。如果您的SpinLock因为其他人长时间持有锁而频繁旋转(这可能已经表明存在一般的设计问题),那么使用普通的互斥锁可能会更好。

票数 42
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/26583433

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档