syk*_*yko 30 c++ multithreading c++11
我实现了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);
}
};
Run Code Online (Sandbox Code Playgroud)
我实现了上面的类,并创建了两个线程,每个线程调用一个相同的Node类实例的add()方法1000万次.
不幸的是,结果不是2000万.我在这里错过了什么?
gex*_*ide 41
问题是一旦失败就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;
}
Run Code Online (Sandbox Code Playgroud)
Mik*_*eMB 34
正如@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);
}
};
Run Code Online (Sandbox Code Playgroud)
资料来源:cppreference
手动指定内存顺序只是一个潜在的性能调整,我从源代码中复制了它.如果简单性比最后一点性能更重要,您可以坚持默认值并只需调用locked.test_and_set() / locked.clear().
顺便说一句:std::atomic_flag是唯一保证无锁的类型,虽然我不知道任何平台,其中oparations std::atomic_bool不是无锁.
更新:正如@David Schwartz,@ Anton和@Technik Empire的评论中所解释的那样,空循环有一些不良影响,如分支未预测,HT处理器上的线程饥饿和过高的功耗 - 简而言之,这是一个非常低效的等待的方式.影响和解决方案是架构,平台和应用程序特定的.我不是专家,但通常的解决方案似乎是cpu_relax() 在linux或YieldProcessor()Windows 上添加一个循环体.
EDIT2:为了清楚起见:这里提供的便携版本(没有特殊的cpu_relax等指令)应该足以满足许多应用程序的要求.如果你的SpinLock旋转很多,因为其他人持有锁很长时间(这可能已经表明一般的设计问题),最好还是使用普通的互斥锁.