英文:
Peterson's algorithm and deadlock
问题
我正在尝试使用一些互斥执行算法进行实验。我已经实现了Peterson算法。它打印出了正确的计数器值,但有时似乎发生了某种死锁,导致执行无限期地停滞。这不应该发生,因为这个算法是无死锁的。
PS:这与编译器优化的问题有关,当提到“良性”数据竞争的危险时经常提到吗?如果是这种情况,如何禁用这样的优化?
PPS:当原子地存储/加载victim字段时,问题似乎消失了,这使得编译器的优化更加可疑。
package main
import (
"fmt"
"sync"
)
type mutex struct {
flag [2]bool
victim int
}
func (m *mutex) lock(id int) {
m.flag[id] = true // 我有兴趣
m.victim = id // 如果你愿意,你可以在我之前执行
for m.flag[1-id] && m.victim == id {
// 当另一个线程在临界区内
// 并且victim是我(我在另一个线程之后表达了兴趣)
}
}
func (m *mutex) unlock(id int) {
m.flag[id] = false // 我不再感兴趣
}
func main() {
var wg sync.WaitGroup
var mu mutex
var cpt, n = 0, 100000
for i := 0; i < 2; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < n; j++ {
mu.lock(id)
cpt = cpt + 1
mu.unlock(id)
}
}(i)
}
wg.Wait()
fmt.Println(cpt)
}
英文:
I am trying to experiment with some mutual execution algorithms. I have implemented the Peterson's algorithm. It prints the correct counter value but sometimes it seems just like some kind of a deadlock had occurred which stalls the execution indefinitely. This should not be possible since this algorithm is deadlock free.
PS: Is this related to problems with compiler optimizations often mentioned when addressing the danger of "benign" data races? If this is the case then how to disable such optimizations?
PPS: When atomically storing/loading the victim field, the problem seems to disappear which makes the compiler's optimizations more suspicious
package main
import (
"fmt"
"sync"
)
type mutex struct {
flag [2]bool
victim int
}
func (m *mutex) lock(id int) {
m.flag[id] = true // I'm interested
m.victim = id // you can go before me if you want
for m.flag[1-id] && m.victim == id {
// while the other thread is inside the CS
// and the victime was me (I expressed my interest after the other one already did)
}
}
func (m *mutex) unlock(id int) {
m.flag[id] = false // I'm not intersted anymore
}
func main() {
var wg sync.WaitGroup
var mu mutex
var cpt, n = 0, 100000
for i := 0; i < 2; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < n; j++ {
mu.lock(id)
cpt = cpt + 1
mu.unlock(id)
}
}(i)
}
wg.Wait()
fmt.Println(cpt)
}
答案1
得分: 2
没有"良性"数据竞争。你的程序存在数据竞争,并且行为是未定义的。
问题的核心在于mutex
的实现。从一个goroutine对共享对象的修改不一定会被其他goroutine观察到,直到这些goroutine使用同步原语进行通信。你从多个goroutine写入mutex.victim
,但可能不会被观察到。你还读取其他goroutine写入的mutex.flag
元素,但不一定会被看到。也就是说,即使其他goroutine更改了变量,for循环可能不会终止。
而且,由于mutex的实现有问题,对cpt
的更新也不一定正确。
要正确实现这个,你需要使用sync/atomic
包。
请参考Go内存模型:https://go.dev/ref/mem
英文:
There is no "benign" data race. Your program has data race, and the behavior is undefined.
At the core of the problem is the mutex
implementation. Modifications made to a shared object from one goroutine are not necessarily observable from others until those goroutines communicate using one of the synchronization primitives. You are writing to mutex.victim
from multiple goroutines, and won't be observed. You are also reading the mutex.flag
elements written by other goroutines, and won't necessarily be seen. That is, there may be cases where the for-loop won't terminate even if the other goroutine changes the variables.
And since the mutex implementation is broken, the updates to cpt
will not necessarily be correct either.
To implement this correctly, you need the sync/atomic
package.
See the Go Memory Model: https://go.dev/ref/mem
答案2
得分: 0
对于Peterson算法(Dekker算法也是如此),你需要确保你的代码是顺序一致的。在Go语言中,你可以使用原子操作来实现这一点。这将防止编译器和硬件搞乱事情。
英文:
For Peterson's algorithm (same goes for Dekker), you need to ensure that your code is sequential consistent. In Go you can do that using atomics. This will prevent the compiler and the hardware to mess things up.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论