A question about evaluation of channel's send statement

Hi everyone!
Please help me figure out the two different results of following code:

package main

import (
    "fmt"
    "time"
)

func main() {
    var num = 10
    var p = &num

    c := make(chan int)

    go func() {
        c <- *p // with this line we will get 11 from channel c
        //c <- num // with this line we will get 10 from channel c
    }()

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

    fmt.Println(p)
}

This is a data race, meaning the program is actually invalid. Compiling it with go install -race and then running it will point that out.

But the effect you are seeing is that a closure takes the values of variables when it’s created (it “closes over” them). The value of num at that point is 10. The value of c is to point at the current value of num, whatever it is at the point *c is evaluated.

1 Like

Agreed, concurrent code should always be checked for race conditions.

However, it seems that the observed behavior is more related to the channel operation itself, rather than to closure semantics. When the goroutine sleeps longer than the main flow, the result of c <- num is 11.

package main

import (
	"fmt"
	"time"
)

func main() {
	var num = 10
	var p = &num

	c := make(chan int)

	go func() {
		time.Sleep(2 * time.Second)
		c <- num
	}()

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

}

(Playground)

Apparently, the value read from the channel is the value at the time the value is fed into the channel. In the original code, c <- num happens before num++, hence the channel contains the old value. With the additional Sleep in the goroutine, c <- num happens after num++ and thus picks up the already incremented value.

Yeah, this has indeed nothing to do with closures (Go closes over the variable num, not the value it had), it’s just raciness all the way down.

jb@unu:~/tmp $ go run race.go 
11
0xc4200140d0
jb@unu:~/tmp $ go run -race race.go 
==================
WARNING: DATA RACE
Write at 0x00c42008c010 by main goroutine:
  main.main()
      /Users/jb/tmp/race.go:20 +0xed

Previous read at 0x00c42008c010 by goroutine 6:
  main.main.func1()
      /Users/jb/tmp/race.go:15 +0x3b

Goroutine 6 (running) created at:
  main.main()
      /Users/jb/tmp/race.go:14 +0xb6
==================
10
0xc42008c010
Found 1 data race(s)
exit status 66
jb@unu:~/tmp $ 

Note 11 in the first run and 10 in the second. (OP code)

With a non-racy program there is no difference between num and *p.

func main() {
	var num = 10
	var p = &num

	c := make(chan int)
	wait := make(chan struct{})

	go func() {
		<-wait
		c <- *p  // 11
		c <- num // 11
	}()

	num++
	close(wait)

	fmt.Println(<-c) // 11
	fmt.Println(<-c) // 11
}

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.