原子操作是否建立了先于关系?

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

Do atomic operations establish a happen-before relation?

问题

我知道在给定的代码中,可能会出现g先打印2然后打印0的情况。

如果我将所有的读写操作改为原子操作,那么是否可以保证如果g先打印2,那么1也会被打印出来呢?

var a, b uint32

func f() {
    atomic.StoreUint32(&a, 1)
    atomic.StoreUint32(&b, 2)
}

func g() {
    fmt.Println(atomic.LoadUint32(&b))
    fmt.Println(atomic.LoadUint32(&a))
}

func main() {
    go f()
    g()
}
英文:

I know it can happen that g prints 2 and then 0 given the following code.

var a, b uint32

func f() {
	a = 1
	b = 2
}

func g() {
    fmt.Println(b)
    fmt.Println(a)
}

func main() {
    go f()
    g()
}

What if I change all read and write to atomic operations? Is that guaranteed that if g prints 2 first, then 1 is also printed?

var a, b uint32

func f() {
    atomic.StoreUint32(&a, 1)
    atomic.StoreUint32(&b, 2)
}

func g() {
    fmt.Println(atomic.LoadUint32(&b))
    fmt.Println(atomic.LoadUint32(&a))
}

func main() {
    go f()
    g()
}

答案1

得分: 3

实际上,它将按照你描述的方式工作。Go编译器不会重新排序原子操作,并且在amd64上使用XCHG来实现原子存储(在其他架构上使用类似的指令):https://github.com/golang/go/blob/release-branch.go1.8/src/cmd/compile/internal/ssa/gen/AMD64.rules#L472

目前(截至Go 1.8),这种行为尚未规定,并且可能会发生变化,请参考https://github.com/golang/go/issues/5045中的讨论以获取更多信息。

英文:

Practically it will work as you described. The Go compiler won't re-order atomic operations and atomic store is implemented using XCHG on amd64 (and similar instructions on other architectures): https://github.com/golang/go/blob/release-branch.go1.8/src/cmd/compile/internal/ssa/gen/AMD64.rules#L472

This behaviour is not specified at the moment (as of Go 1.8) and might change, see discussion at https://github.com/golang/go/issues/5045 for more insights.

答案2

得分: 2

不,没有同步,因此没有"happens before"(发生在之前)的关系。

goroutine之间的同步是通过通道通信和锁操作来完成的。

内存模型中的一个关键段落是:

在单个goroutine内,读取和写入必须表现得好像它们按程序指定的顺序执行。也就是说,编译器和处理器只有在重新排序不会改变该goroutine内的行为时,才能重新排序在单个goroutine内执行的读取和写入。由于这种重新排序,一个goroutine观察到的执行顺序可能与另一个goroutine感知到的顺序不同。例如,如果一个goroutine执行a = 1; b = 2;,另一个goroutine可能在观察到a的更新值之前观察到b的更新值。

英文:

No. There's no synchronization, so there is no "happens before" relationship.

Synchronization between goroutines is done via channel communication and lock operations.

A key paragraph in the memory model is:

> Within a single goroutine, reads and writes must behave as if they executed in the order specified by the program. That is, compilers and processors may reorder the reads and writes executed within a single goroutine only when the reordering does not change the behavior within that goroutine as defined by the language specification. Because of this reordering, the execution order observed by one goroutine may differ from the order perceived by another. For example, if one goroutine executes a = 1; b = 2;, another might observe the updated value of b before the updated value of a.

答案3

得分: 1

这里给出的其他答案已经过时,不再有效。原子操作确实会引发happens-before边缘:

https://go.dev/ref/mem#atomic

所以这段代码可以正常工作:

var a, b uint32

func f() {
    atomic.StoreUint32(&a, 1)
    atomic.StoreUint32(&b, 2)
}

func g() {
    fmt.Println(atomic.LoadUint32(&b))
    fmt.Println(atomic.LoadUint32(&a))
}

func main() {
    go f()
    g()
}

但是以下代码也可以正常工作(所以如果看到b==2,那么也会看到a==1)。

var a, b uint32

func f() {
    a = 1                             // (1)
    atomic.StoreUint32(&b, 2)       // (2)
}

func g() {
    if atomic.LoadUint32(&b) == 2 {   // (3)
        fmt.Println(a)              // (4)
    }
}

func main() {
    go f()
    g()
}

由于有序前序关系,(1)和(2)之间以及(3)和(4)之间存在happens-before边缘。由于同步前序关系,(2)和(3)之间存在happens-before边缘。

并且由于happens-before是传递的,(1)和(4)之间存在happens-before边缘。

英文:

The other answers given here are pretty old and not valid any longer. Atomic operations do induce happens-before edges:

https://go.dev/ref/mem#atomic

So this works fine:

var a, b uint32

func f() {
    atomic.StoreUint32(&a, 1)
    atomic.StoreUint32(&b, 2)
}

func g() {
    fmt.Println(atomic.LoadUint32(&b))
    fmt.Println(atomic.LoadUint32(&a))
}

func main() {
    go f()
    g()
}

But also the following should work fine (so if b==2 is seen, then a==1 is seen as well).

var a, b uint32

func f() {
    a=1                             (1)
    atomic.StoreUint32(&b, 2)       (2)
}

func g() {
    if(atomic.LoadUint32(&b)==2){   (3)
        fmt.Println(a)              (4)
    }
}

func main() {
    go f()
    g()
}

There are happens-before edges between (1) and (2) and between (3) and (4) due to sequenced before. And there is a happens-before edge between (2) and (3) due to synchronized before.

And since happens-before is transitive, there is a happens-before edge between (1) and (4).

huangapple
  • 本文由 发表于 2017年2月15日 00:21:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/42231153.html
匿名

发表评论

匿名网友

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

确定