On Wed, Sep 06, 2017 at 04:26:09AM -0700, T L wrote:

> > > It is just weird that the evaluation timing of *p is different to other 
> > expressions, such as, *p+0, *p*1, func()int{return *p}m etc. 
> >
> > The value depends on a data race so it's entirely undefined in all cases. 
> > That the actual outcome then depends on trivial differences that may affect 
> > CPU loads/stores, instruction ordering, register usage, etc isn't 
> > surprising. 
> 
> I don't think it is so complex. It is simply that gc adopts a different 
> route for evaluate pointer dereference.

It's not complex, it's a simple data race.

In your code

    var num = 10
    var p = &num

    c := make(chan int)

    go func() {
        c <- func()int{return *p}()
    }()

    time.Sleep(time.Second)
    num++
    fmt.Println(<-c)

two goroutines -- the main one and the one you spawned are accessing the
variable "num" concurrently without any synchronization.

Some assorted points to may be make this more clear:

* Sleeping for "just enough" time is not a form of synchronization.

* You might be driven away by using a function literal which is
  immediately executed but that's pretty much equivalent to just doing

    c <- *p

  which supposedly makes the data race over the variable "num"
  even more obvious.

* You might keep false assumption that function literals producing
  function values close over values -- that is, they work sort of as
  code templates.  This is wrong: they close over variables, so in your
  example the function value you're constructing before running it on
  a separate goroutine closes over the variable "p", not the value
  obtained by dereferencing it at the time of producing that function
  value.


To explain what happens, step by step, the statement

  go func() {
      c <- func()int{return *p}()
  }()

in your example:

1) Creates a function value which is a closure consisting of the
   anonymous function

     func() {
       c <- func()int{return *p}()
     }

   and a reference to the variable "p" in the scope of the function
   main().

 2) Spawns a goroutine and tells it to execute the function value
    created on the previous step.

 3) When run, the function executes the function value produced by
    the expression on the right side of the channel send statement.

    That function value is itself a closure, closing over the variable
    "p" in the scope of its lexical parent function -- the closure
    created on step 1.

    The pointer gets dereferenced and this action accesses the memory
    location "backing" the variable "num".
    The same memory is accessed concurrently from the main goroutine.

Since there is no synchronization done for _this_ access, you have
a data race.  The fact you have a channel send/receive has nothing
to do with regard to accessing the memory of the "num" variable
since both goroutines do it before attempting its respective channel
send/receive operations.  During that time, they may have being run on
different hardware CPUs or one of the goroutines might have no chance to
run at all.  Running on different CPUs involves a whole lot of
interesting problems, of which the most glaring are reordering of memory
accesses and coherence problems of CPU caches.

Please read [1, 2] and [3] (which is written by one of the Go core devs).

1. http://preshing.com/20120515/memory-reordering-caught-in-the-act/
2. 
http://preshing.com/20120710/memory-barriers-are-like-source-control-operations/
3. 
https://software.intel.com/en-us/blogs/2013/01/06/benign-data-races-what-could-possibly-go-wrong

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to