[英]C++11 Implementation of Spinlock using <atomic>
我实现了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万次。
不幸的是,结果不是2000万。 我在这里错过了什么?
问题是compare_exchange_weak
一旦失败就会更新unlatched
变量。 从compare_exchange_weak
的文档:
将原子对象包含的值的内容与预期值进行比较: - 如果为true,则用val替换包含的值(如store)。 - 如果为false,则将其替换为包含的值。
即第一失败后compare_exchange_weak
, unlatched
将更新为true
,那么下一个循环周期将尝试compare_exchange_weak
true
与true
。 这成功了,你只是拿了一个由另一个线程持有的锁。
解决方案:确保设置unlatched
回false
每个前compare_exchange_weak
,如:
while(!latch.compare_exchange_weak(unlatched, true, std::memory_order_acquire)) {
unlatched = false;
}
正如@gexicide所提到的,问题是compare_exchange
函数使用原子变量的当前值更新expected
变量。 这也是为什么你必须首先使用未unlatched
的局部变量的原因。 要解决此问题,您可以在每次循环迭代中将unlatched
设置为false。
但是,使用std::atomic_flag
来代替使用compare_exchange
,而不是使用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
上的std::atomic_bool
不是免费的。
更新:正如@David Schwartz,@ Anton和@Technik Empire的评论中所解释的那样,空循环有一些不良影响,如分支未预测,HT处理器上的线程饥饿和过高的功耗 - 所以简而言之,这是一个非常低效的等待的方式。 影响和解决方案是架构,平台和应用程序特定的。 我不是专家,但通常的解决方案似乎是在linux上添加cpu_relax()
或在循环体上添加窗口上的YieldProcessor()
。
EDIT2:为了清楚起见:这里提供的便携版本(没有特殊的cpu_relax等指令)应该足以满足许多应用程序的要求。 如果你的SpinLock
旋转很多,因为其他人持有锁很长时间(这可能已经表明一般的设计问题),最好还是使用普通的互斥锁。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.