前言
我最近遇到了一些同步问题,这导致我使用自旋锁和原子计数器。然后我又搜索了一下,这些是如何工作的,并找到了std::memory_order和内存屏障(mfence
,lfence
和sfence
)。
所以现在,似乎我应该对自旋锁使用获取/释放,对计数器使用放松。
一些参考
x86 MFENCE - 内存围栏
x86 LOCK - 断言 LOCK# 信号
问题
这三个操作的机器代码(编辑:见下文)是什么(lock = test_and_set,unlock = clear,increment = operator++ = fetch_add),默认(seq_cst)内存顺序和获取/释放/放松(按这些顺序)三个操作)。有什么区别(哪些内存屏障在哪里)和成本(多少 CPU 周期)?
目的
我只是想知道我的旧代码(未指定使用的内存顺序 = seq_cst)到底有多糟糕,以及我是否应该创建一些class atomic_counter
派生自std::atomic
但使用宽松的内存排序 (以及在某些地方使用获取/释放而不是互斥锁的良好自旋锁)。 ..或使用来自boost库的东西——到目前为止我一直避免boost)。
我的知识
到目前为止,我确实理解自旋锁保护的不仅仅是它本身(但也保护了一些共享资源/内存),因此,必须有一些东西可以使多个线程/核心的一些内存视图保持一致(即那些获取/释放和内存栅栏) ) . 原子计数器只为自己而存在,只需要那个原子增量(不涉及其他内存,我读它时并不真正关心它的值,它信息丰富,可能是几个周期的旧,没问题)。有一些LOCK
前缀和一些指令,比如xchg
隐含地拥有它。我的知识到此结束,我不知道缓存和总线是如何真正工作的以及背后是什么(但我知道现代 CPU 可以重新排序指令,并行执行它们并使用内存缓存和一些同步)。谢谢你的解释。
PS:我现在有旧的32位PC,只能看到lock addl
和简单xchg
,没有别的-所有版本看起来都一样(解锁除外),memory_order在我的旧PC上没有区别(除了解锁,释放使用move
而不是xchg
)。对于 64 位 PC 来说会是这样吗?(编辑:见下文)我必须关心内存顺序吗?(回答:不,不多,解锁时释放可以节省几个周期,仅此而已。)
编码:
#include <atomic>
using namespace std;
atomic_flag spinlock;
atomic<int> counter;
void inc1() {
counter++;
}
void inc2() {
counter.fetch_add(1, memory_order_relaxed);
}
void lock1() {
while(spinlock.test_and_set()) ;
}
void lock2() {
while(spinlock.test_and_set(memory_order_acquire)) ;
}
void unlock1() {
spinlock.clear();
}
void unlock2() {
spinlock.clear(memory_order_release);
}
int main() {
inc1();
inc2();
lock1();
unlock1();
lock2();
unlock2();
}
g++ -std=c++11 -O1 -S ( 32bit Cygwin,缩短输出)
__Z4inc1v:
__Z4inc2v:
lock addl $1, _counter ; both seq_cst and relaxed
ret
__Z5lock1v:
__Z5lock2v:
movl $1, %edx
L5:
movl %edx, %eax
xchgb _spinlock, %al ; both seq_cst and acquire
testb %al, %al
jne L5
rep ret
__Z7unlock1v:
movl $0, %eax
xchgb _spinlock, %al ; seq_cst
ret
__Z7unlock2v:
movb $0, _spinlock ; release
ret
x86_64bit 的更新:(mfence
见unlock1
)
_Z4inc1v:
_Z4inc2v:
lock addl $1, counter(%rip) ; both seq_cst and relaxed
ret
_Z5lock1v:
_Z5lock2v:
movl $1, %edx
.L5:
movl %edx, %eax
xchgb spinlock(%rip), %al ; both seq_cst and acquire
testb %al, %al
jne .L5
ret
_Z7unlock1v:
movb $0, spinlock(%rip)
mfence ; seq_cst
ret
_Z7unlock2v:
movb $0, spinlock(%rip) ; release
ret