https://www.justsoftwaresolutions.co.uk/threading/petersons_lock_with_C++0x_atomics.html
我写了评论,问了两个问题,对安东尼的回复还有另一个问题。
这是回复:
"1. flag0和flag1变量上的acquire/release是必要的,以确保它充当锁:unlock中的release store与next lock中的acquire-load同步,以确保在锁的同时修改数据被持有的现在对第二个线程可见。”
我用 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 上,'store' 有一个释放语义,'load' 有一个获取语义(根本原因是在 x86/64 上只有 store 加载重新排序),并且 'lock.flag[id]= false' 是一个'store','lock.flag[1 - id]' 是一个'load',所以在 Dmitriy 的实现中不需要对 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;
}