Skip to content

golang documents should make it clear whether or not reading/writing scalar type variables is atomic. #16048

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
yaxinlx opened this issue Jun 13, 2016 · 9 comments

Comments

@yaxinlx
Copy link

yaxinlx commented Jun 13, 2016

  1. What version of Go are you using (go version)?

1.6

  1. What operating system and processor architecture are you using (go env)?

Debian 8, Intel amd64 CPU.

  1. What did you do?

I wrote a small program to test if reading/writing of scalar type variables is atomic.

package main

import "runtime"

const A, B = 12345, 67890
var c = A

func f() {
    for {
        c = A
    }
}

func g() {
    for {
        c = B
    }
}

func main() {
    if runtime.NumCPU() < 3 {
        runtime.GOMAXPROCS(3)
    }

    go f()
    go g()

    for {
        t := c
        if t != A && t != B {
            println("!!!!!!!!!! c =", t)
            panic("c != A && c != B")
        }
    }
}
  1. What did you expect to see?

expect panic if reading/writing of scalar type variables is not atomic

  1. What did you see instead?

never panic.

The official golang documents don't mention whether or not reading/writing scalar type variables is atomic. There are some pieces of vague info in other pages, such as:

Many 3rd party articles and group posts say reading/writing scalar type variables is NOT atomic.
Are their statements correct for any processor architecture and OSes? or just for some ones?

I really hope golang official documents can make clear on this point.

@ianlancetaylor
Copy link
Contributor

"Atomic" is a very overloaded word in this context. What precisely do you mean?

I think that the Go memory model, which you reference, already says what we want to say. It "specifies the conditions under which reads of a variable in one goroutine can be guaranteed to observe values produced by writes to the same variable in a different goroutine." I don't see a reason to be more specific.

Your example program only tells you what happens on one particular machine on one particular implementation. Go is intentionally defined by a specification, not an implementation. There are multiple implementations of Go, and even a single implementation behaves differently on different processors.

@randall77
Copy link
Contributor

We did go through some effort to make sure pointer writes are atomic. Mostly to keep the garbage collector happy, but generally to prevent racy programs from going completely off the rails. See #13160.

@adg
Copy link
Contributor

adg commented Jun 14, 2016

The memory model document, which is linked, is pretty explicit:

Programs that modify data being simultaneously accessed by multiple goroutines must serialize such access.

To serialize access, protect the data with channel operations or other synchronization primitives such as those in the sync and sync/atomic packages.

If you must read the rest of this document to understand the behavior of your program, you are being too clever.

Don't be clever.

@yaxinlx
Copy link
Author

yaxinlx commented Jun 14, 2016

@ianlancetaylor, I hope the official docs can make it clear, for the offical gosdk, on which processor architectures and OSes, writes/reads scalar variable are atomic (or not), instead of a vague mention in a side article.

@ianlancetaylor and could you provide an example on x64 linux to show writes/reads scalar variable aren't atomic?

@randall77 thanks for the clarification, but only pointer writes are atomic? not for pointer reads?

@adg but I can't find an example to support writes/reads scalar variable aren't atomic on x64 linux.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor, I hope the official docs can make it clear, for the offical gosdk, on which processor architectures and OSes, writes/reads scalar variable are atomic (or not), instead of a vague mention in a side article.

You didn't answer my question about what precisely you mean by "atomic." It's not a simple question. For example, see http://en.cppreference.com/w/cpp/atomic/memory_order. That level of detail is appropriate for C++, but it is not something we aim for in Go. The Go guidelines try to be simple: use channels, use mutexes, use the sync/atomic package if you must.

@ianlancetaylor and could you provide an example on x64 linux to show writes/reads scalar variable aren't atomic?

No, I can't, because 1) you haven't defined your terms; 2) x86 processors always use a strict memory ordering model so there are many race conditions that can not be demonstrated on x86 but can be demonstrated on, for example, ARM and PPC processors. (Here is a pretty good paper on more relaxed memory models used on non-x86 processors: https://www.cl.cam.ac.uk/~pes20/ppc-supplemental/test7.pdf.)

In short, you are asking for something that is simple and we are telling you that it is complicated. We are trying to make it simple by not answering your question, which would require many complex details, but instead addressing the overall issue at a higher level: use channels, use mutexes, use sync/atomic if you must.

@randall77
Copy link
Contributor

Pointer reads are currently atomic as well.

As an example in the other direction, complex64 is not currently atomic on amd64.

There are two different questions here that you're somewhat conflating. One is what current implementations do or do not provide atomicity for. The other is what atomicity guarantees the spec provides. The former is an interesting question about current implementations, but ultimately not very useful. The latter is what kind of guarantees we want to provide across all architectures and across all time. We don't want to commit to anything here, it may be too expensive to provide particular guarantees on some platforms. The ordering guarantees provided by locks & channels is all we want to commit to, and is all that is needed for 99% of programs.

@adg adg closed this as completed Jun 14, 2016
@yaxinlx
Copy link
Author

yaxinlx commented Jun 15, 2016

thanks all for the information!

@yaxinlx
Copy link
Author

yaxinlx commented Jul 6, 2016

@randall77 can I ask what technology is used to make sure pointer writes/reads atomic?

@ianlancetaylor
Copy link
Contributor

@yaxinlx As I think I keep saying, "atomic" doesn't mean just one thing.

Keith is saying that the compiler generates the machine instructions that ensure that if two different processors write a pointer value to the same memory location simultaneously, other processors will see one value or the other value, but not a mixture. The "technology" is simply using whatever instructions the processor supports to make this happen.

However, the compiler does not use instructions that guarantee anything about the visibility of those writes to other processors. It's quite possible, on some architectures, that if processors 1 and 2 write a pointer value simultaneously then processor 3 will see the value written by processor 1 and processor 4 will see the value written by processor 2.

That said, please take further discussion of this off this issue and to a forum. See https://golang.org/wiki/Questions . Thanks.

@golang golang locked and limited conversation to collaborators Jul 6, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants