package main
import (
"fmt"
"time"
)
func main() {
var num = 10
var p = &num
c := make(chan int)
go func() {
time.Sleep(time.Second)
c <- *p //-----> 11
//c <- num //----->10
}()
time.Sleep(2 * time.Second)
num++
fmt.Println(<-c)
fmt.Println(p)
return
}
为什么 *p 和num的结果不一样
有疑问加站长微信联系(非本文作者)

你这个有 data race,结果具有不确定性
@channel 所以两个的睡眠时间是不一样的呀
https://golang.org/ref/spec#Send_statements
我跟踪了下 chan send 和 chan receive 的实现,发现
c <- *p
和c <- num
有如下不同:<-
的实现chansend
发送的值,用的数据类型是unsafe.Pointer
,分别打印c <- *p
和c <- num
对应发送值的指针,发现发送*p
时,指针直接是 num 的地址,而发送num
时,指针是拷贝 num 之后的地址。这样一来,发送*p
后,num 的变化会反应到 chan 接收方,而发送num
却不会。这里确实有点坑!
相关源码:http://docs.studygolang.com/src/runtime/chan.go 中的
chansend
和chanrecv
感谢回答,我也看到这两个的源码了,但是不知道,怎么去证明,只能对这两个地方反汇编,但是水平太次,没能看懂指令集 能不能请教一下,你是怎么分别打印的? 是直接调试的go的源码? 能否指点一二
直接修改 go 源码,通过 print 直接输出,然后重新编译 go。
ps: 刚才你这个评论发出来两个一样的,是第一次提交提示没成功还是什么?我查查是不是有 bug。
@polaris 没有,是我手动回复了两次 一次是引用,一次是回复,回复重了
不好意思,我想自己验证一下,但是我在编译go源码的时候 发现没有这个文件,zversion.go,我的编译环境是windows上的,并且源码是刚从github上下的,请问我应该怎么做
我已经从网上查了,但并没有得到解决方法
go 的版本是?你之前的版本是源码安装的吗?
windows10的版本是go1.8 之前是使用二进制包安装的
这个问题很有趣,time.Sleep()并不能保证
c <- *p
是先于num++执行的,并且貌似没有同步的办法让c <- *p
先于num++执行???
windows 下先安装 Cygwin、MinGW 或 TDM-GCC 试试,源码编译需要 gcc
可以使用g1.43版的go编译go1.9 , 我已经验证你的说法了,如果是num的话,在chansend1中,elem的地址确实不是num的地址,可能是编译器做了一份拷贝之后的地址
本问题完结
经过查看chansend1的源码,并且添加了如下注释
增加源代码的打印信息
使用 c<-num 时的打印 如下
使用c<-*p 时的打印如下
应该可以证明@polaris 假设是成立的
这个问题就是data race的问题,规范里面对
Send statements
的说明是:Both the channel and the value expression are evaluated before communication begins
只要在通过同步方式保证
c <- *p
是先于num++
执行的情况下,从c里面读取到的值一定是10,不管是c <- *p
还是c <- num
如果不能保证
c <- *p
和num++
的同步,结果就是不确定的,并且考虑c <- *p
和c <- num
的结果为什么不同也没多大意义那请问,在同一个位置,为什么num和*p在chansend1中入参不一样
我在golang-nuts上问了这个问题,下面是一些回复:
1.
2.
golang-nuts 上该问题的地址可以发下
https://groups.google.com/forum/#!topic/golang-nuts/J3kXXZivlHA
规范里面对Send statements的说明是:
问题的关键在于
communication begins
发生在什么时候?这里有4个时间点:
A:
num++
B:
<-c
C:
c <- *p
D:
*p
这个表达式求值communication begins
可能指的时间点是B或C,就是说D可能发生在goroutine里面time.Sleep(time.Second)
之后,B或C之前的任意时刻根据Go memory model,有几个happen before是可以确定的:
A在B之前,B在C之前
那么就有下面两种情况:
在A和D的执行顺序不确定的情况下,同一程序每次执行的结果都可能不同,更不要指望把D里面的
*p
换成num
后两者执行结果每次都相同实际应用中也不会有这样用Go用的不彻底的代码:用channel传递一个全局变量,却没有在从channel里面获取到这个变量之后再修改
Do not communicate by sharing memory; instead, share memory by communicating.
研究的很深入
之前一直没看懂,今天在手机上看到以一篇博客突然很有感触,回看你的回复,确实分析的很细,回去用电脑细看,感谢
规范里面对Send statements的说明是:
问题的关键在于communication begins发生在什么时候?
这里有4个时间点:
A:num++
B1:<-c begin
B2: <-c return
C1: c <- *p begin
C2: c <- *p return
D: *p这个表达式求值
根据Go memory model,有几个happen before是可以确定的:
C1在B2之前,B2在C2之前, A在B1之前,B1在B2之前
C1--------B2-----C2
A---B1----
communication begins的时间点应该是B1与B2之间,并且同时也是C1与C2之间,按上图就是:max(B1, C1)到B2之间的一个时间点,假设为X
就是说D发生在goroutine里面time.Sleep(time.Second)之后,X之前
那么就有下面两种情况:
如果B1>C1(C1发生在B1之前),那么X在B1与B2之间,A一定在X之前,但是D也在X之前,A和D的顺序不能确定
如果C1>B1(B1发生在C1之前),那么X在C1与B2之间,A一定在X之前,但是D也在X之前,A和D的顺序不能确定