我实现了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万次。
很不幸,结果不是两千万,我错过了什么
3条答案
按热度按时间jutyujz01#
问题是
compare_exchange_weak
在unlatched
变量失败后会更新它。从compare_exchange_weak
的文档中可以看到:将原子对象的包含值的内容与预期值进行比较:-如果为true,则将包含值替换为val(类似于store)。-如果为false,则将预期值替换为包含值。
也就是说,在第一个失败的
compare_exchange_weak
之后,unlatched
将被更新为true
,所以下一个循环迭代将尝试compare_exchange_weak
true
和true
。这成功了,你只是得到了另一个线程持有的锁。解决方案:确保在每个
compare_exchange_weak
之前将unlatched
设置回false
,例如:字符串
hpcdzsge2#
正如@gexicide所提到的,问题是
compare_exchange
函数用原子变量的当前值更新expected
变量。这也是为什么你必须首先使用局部变量unlatched
的原因。为了解决这个问题,你可以在每次循环迭代中将unlatched
设置回false。然而,与其使用
compare_exchange
来处理它的接口不太适合的事情,不如使用std::atomic_flag
来处理:字符串
来源:cppreference
手动指定内存顺序只是一个小的潜在性能调整,我从源代码中复制了这一点。如果简单性比最后一点性能更重要,您可以坚持使用默认值,只需调用
locked.test_and_set() / locked.clear()
。顺便说一句:
std::atomic_flag
是唯一保证无锁的类型,尽管我不知道任何平台,在std::atomic_bool
上的操作不是无锁的。**更新:**正如@大卫施瓦茨,@安东和@Technik Empire的评论中所解释的那样,空循环有一些不良的影响,如分支错误预测,HT处理器上的线程饥饿和过高的功耗-所以简而言之,这是一种非常低效的等待方式。影响和解决方案是架构,平台和应用程序特定的。我不是Maven,但通常的解决方案似乎是在Linux上添加
cpu_relax()
或在Windows上添加YieldProcessor()
到循环体。SpinLock
因为其他人长时间持有锁而旋转很多(这可能已经表明了一个一般的设计问题),那么无论如何使用普通的互斥锁可能更好。1cklez4t3#
内存优化只在本地工作,因此,这些内存管理方法(td::memory_order_acquire和std::memory_order_release)是无用的。