Memory 带mfence/lfence/sfence的第二线程可见锁中数据修改的peterson算法
我写了评论,问了两个问题,还有一个问题是关于安东尼的回答。Memory 带mfence/lfence/sfence的第二线程可见锁中数据修改的peterson算法,memory,memory-fences,memory-barriers,Memory,Memory Fences,Memory Barriers,我写了评论,问了两个问题,还有一个问题是关于安东尼的回答。 答复如下: “1.对flag0和flag1变量的获取/释放是必要的,以确保它充当锁:解锁中的释放存储与下一个锁中的获取加载同步,以确保在持有锁时修改的数据现在对第二个线程可见。” 我用C写了一个彼得森锁 typedef struct { volatile bool flag[2]; volatile int victim; } peterson_lock_t; void peterson_lock_init(pete
答复如下: “1.对flag0和flag1变量的获取/释放是必要的,以确保它充当锁:解锁中的释放存储与下一个锁中的获取加载同步,以确保在持有锁时修改的数据现在对第二个线程可见。” 我用C写了一个彼得森锁
typedef struct {
volatile bool flag[2];
volatile int victim;
} peterson_lock_t;
void peterson_lock_init(peterson_lock_t &lock) {
lock.flag[0] = lock.flag[1] = false;
lock.victim = 0;
}
void peterson_lock(peterson_lock_t &lock, int id) {
lock.flag[id] = true;
lock.victim = id;
asm volatile ("mfence" : : : "memory");
while (lock.flag[1 - id] && lock.victim == id) {
};
}
void peterson_unlock(peterson_lock_t &lock, int id) {
lock.flag[id] = false;
}
我测试过了,我认为它是正确的,对吗
如果它是正确的,我的问题是我是否需要添加sfence和lfence以“确保在持有锁时修改的数据现在对第二个线程可见”?
像这样,
void peterson_lock(peterson_lock_t &lock, int id) {
lock.flag[id] = true;
lock.victim = id;
asm volatile ("mfence" : : : "memory");
asm volatile ("lfence" : : : "memory"); // here, I think this is unnecessary, since mfence will flush load buffer
while (lock.flag[1 - id] && lock.victim == id) {
};
}
void peterson_unlock(peterson_lock_t &lock, int id) {
asm volatile ("sfence" : : : "memory"); // here
lock.flag[id] = false;
}
我认为没有必要这样做。
我的理解是,在x86/64上,“存储”具有发布语义,“加载”具有获取语义(根本原因是在x86/64上,只有存储加载重新排序),
而'lock.flag[id]=false'是'store','lock.flag[1-id]'是'load',
因此,在Dmitry的实现中,不需要在flag0和flag1上执行获取/发布之类的操作
编辑@Anthony
非常感谢你的重播。
是的,我需要避免编译器重新排序。
那么,下面的修改是否正确?
因为对于x86,只需要在“peterson_unlock”中禁用编译器重新排序
void peterson_lock(peterson_lock_t &lock, int id) {
lock.flag[id] = true;
lock.victim = id;
asm volatile ("mfence" : : : "memory");
while (lock.flag[1 - id] && lock.victim == id) {
};
}
void peterson_unlock(peterson_lock_t &lock, int id) {
asm volatile ("" : : : "memory"); // here, forbidden compiler reorder
lock.flag[id] = false;
}
原子操作及其内存顺序标志的使用不仅仅是选择指令。它还影响编译器的优化程序
volatile
读写不能相互重新排序,必须发出,但可以与其他代码自由重新排序
不同步地从多个线程访问volatile
非原子变量是未定义的行为,就像非volatile
非原子变量一样。
因此
可重新排序为
int a;
a=42;
peterson_lock(some_lock,0);
peterson_unlock(some_lock,0);
或
两者都不保留锁定功能
由于具有内存顺序\u释放
顺序的存储可确保使用内存顺序\u获取
顺序的后续加载可以看到以前的写入,因此这本质上意味着编译器无法对解锁后的先前存储重新排序,如果将原子操作与内存\u顺序\u释放一起使用
类似地,由于使用内存\u顺序\u获取
顺序的加载可确保来自另一个线程的写入在以前不可见的情况下现在可见,这本质上意味着编译器无法在锁定之前对后续加载重新排序,如果将原子操作与内存\u顺序\u获取一起使用
简言之:在锁定和解锁中需要内存顺序约束,不仅是为了选择指令,而且(同样重要的)是为了对编译器产生影响
对于x86,relaxed、acquire和seq_cst加载都是无限制的mov
指令,但它们对编译器的影响是完全不同的
如果不需要原子操作的内存顺序语义,请对所有操作使用内存顺序
。这将确保操作的原子性(并避免未定义的行为),而无需添加额外的排序要求。因此,当您试图使用volatile
变量进行同步时,您应该使用具有适当内存顺序(包括内存顺序
)的原子
变量。
> <>强> > < >强> >需要在C++代码中添加附加的<代码> ASM>代码>语句。原子操作和围栏功能足够了
您的代码仍然不正确,因为
标志
变量和受害者
变量都被多个线程触及,并且不是原子变量。请检查问题的编辑版本。非常感谢!
int a;
a=42;
peterson_lock(some_lock,0);
peterson_unlock(some_lock,0);
int a;
peterson_lock(some_lock,0);
peterson_unlock(some_lock,0);
a=42;