如何理解Go内存模型中的“不正确的同步”示例?

huangapple go评论108阅读模式
英文:

How to understand the "Incorrect synchronization" samples in Go Memory Model

问题

我刚开始学习golang,当我阅读《Go内存模型》(https://golang.org/ref/mem#tmp_10)时,我对其中关于“另一个错误的习惯是忙等待一个值”的说法有疑问。

它说:

“更糟糕的是,没有保证main函数能够观察到对done的写入,因为这两个线程之间没有同步事件。main函数中的循环不能保证结束。”

我知道在setup()函数中,对'a'和'done'的写入顺序是不确定的。我的问题是:为什么不能保证main函数能够看到对done的写入?

谢谢。

英文:

I am just starting learning golang, when reading Go Memory Model I got a question to understand what it says about "Another incorrect idiom is busy waiting for a value,"

var a string
var done bool
func setup() {
    a = "hello, world"
    done = true
}
func main() {
    go setup()
    for !done {
    }
    print(a)
}

It says:

>"Worse, there is no guarantee that the write to done will ever be observed by main, since there are no synchronization events between the two threads. The loop in main is not guaranteed to finish."

I know that the order of writes to 'a' and 'done' is not deterministic in setup(), My question is: why main is not guaranteed to see the write to done?

Thank you

答案1

得分: 5

package main

var a string

var done bool

func setup() {
    a = "hello, world"
    done = true
}
func main() {
    go setup()
    for !done {
    }
    println(a)
}

你有两个 goroutine,main 和 setup。假设它们在不同的线程和不同的 CPU 上运行,并且有本地内存缓存。假设 main 和 setup 都将 done 变量读入本地 CPU 内存缓存中。setup goroutine 在其本地内存缓存中更新 done,这是一种延迟写入。由于独立的 main 和 setup goroutine 之间没有缓存同步事件,因此无法保证缓存一致性,无法保证主内存和两个 CPU 内存缓存将被同步。不同的硬件处理方式不同。在 Go 中,只能保证最低公共分母。

参见缓存一致性

英文:
package main

var a string

var done bool

func setup() {
	a = "hello, world"
	done = true
}
func main() {
	go setup()
	for !done {
	}
	println(a)
}

You have two goroutines, main and setup. For example, assume that they are running on separate threads on separate CPUs with local memory cache. Assume that both main and setup read the done variable into a local CPU memory cache. The setup goroutine updates done in its local memory cache, which does lazy writes. Since there is no cache synchronization event between the independent main and setup goroutines there is no guarantee of cache coherency, no guarantee that main memory and both CPU memory caches will be synchronized. Different hardware handles this differently. In Go, only the lowest common denominator can be guaranteed.

See Cache coherence.

答案2

得分: 2

Go的内存模型是一种公理化内存模型,它是通过happens-before关系来定义的。

因此,如果事件A happens-before事件B,那么B应该能够看到A和A之前的所有内容。

在Golang中,单个Goroutine中的所有操作都会由于序列之前的规则创建这种happens-before边缘。因此,Goroutine应该始终能够看到自己的更改。

但是,如果在不同的Goroutine之间共享状态,如果没有happens-before边缘,事情可能会变得棘手。

对于共享位置的两个内存操作,如果其中至少一个是写操作,则它们是冲突的。当你有两个并发的冲突内存操作,所以它们之间没有happens-before边缘时,就会发生数据竞争。

一旦发生数据竞争,就可能出现奇怪的问题。通常涉及原子性、可见性和重排序问题。

在你的示例中,'a'和'done'存在数据竞争。

func setup() {
    a = "hello, world" (1)
    done = true (2)
}
func main() {
    go setup()
    for !done { (3)
    }
    println(a) (4)
}

例如,setup函数中的两个存储操作可能会被重新排序。要解决这个问题,你需要使用原子操作来更新'done'。

在这种情况下,由于序列之前,(1)和(2)之间存在happens-before边缘。并且(3)和(4)之间也存在happens-before边缘。而(2)和(3)之间由于同步之前存在happens-before边缘。由于happens-before是传递的,所以(1)和(4)之间存在happens-before边缘。因此,你可以得到这样的保证:当你读取'done=true'时,你将看到'a=hello world'。

英文:

The go memory model is an axiomatic memory model and is defined in terms of a happens-before relation.

So if A happens-before B, then B should see A and everything before A.

In Golang all actions in a single Goroutine create such happens-before edges due to the sequence-before rule. So a Goroutine should always be able to see its own changes.

But if you share state between different Goroutines, things can become problematic if there is no happens-before edge.

2 Memory actions to a shared location are conflicting when at least one of them is a write. When you have 2 conflicting memory actions that are concurrent, so there is no happens-before edge between them, then you have a data race.

And as soon as you have a data race, weird problems can happen. So typically atomicity, visibility, and reordering problems.

In your example, there is a data race on 'a' and 'done'.

func setup() {
    a = "hello, world" (1)
    done = true (2)
}
func main() {
    go setup()
    for !done { (3)
    }
    println(a) (4)
}

The 2 stores in the setup function could be reordered for example. To fix this problem you would need to update 'done' using an atomic.

In that case, you have a happens-before edge between (1) and (2) due to sequenced-before. And also between (3) and (4). And between (2) and (3) there is a happens-before edge due to synchronized-before. Since happens-before is transitive, there is a happens-before edge between (1) and (4). And therefore you get the guarantee, that when you read 'done=true', you will see 'a=hello world'.

huangapple
  • 本文由 发表于 2017年5月8日 17:19:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/43843960.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定