Do atomic operations establish a happen-before relation?
Asked Answered
T

3

6

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()
}
Tavish answered 14/2, 2017 at 16:21 Comment(1)
Did you try it? In most cases f() won't be executed at all.Litre
B
3

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.

Bum answered 14/2, 2017 at 22:26 Comment(0)
L
2

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.

Litre answered 14/2, 2017 at 16:27 Comment(2)
correct me if I'm wrong: let's say ev1 as a = 1, ev2 as b= 2, ev3 as print a, ev4 as print b. Since there is no synchronisation can happen that ev3 and ev4 are executed before ev1 & ev2. But if ev3 print 2 this mean also that ev1 and ev2 are already executed so ev4 will print for sure 1. Am I wrong?Huth
@Tinwor: No, that's not guaranteed, especially without atomic operations. Each goroutine may be in a different thread, on a different core, on a different NUMA node and viewing different memory. Even when using atomic operations, the implicit order of operations technically only applies within a goroutine, so I don't think it's be guaranteed by the memory model.Litre
A
1

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).

Abreu answered 31/8, 2022 at 4:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.