刚刚接触go语言,对于其内存模型不是很理解。能够明白通道通信,锁等内容。。但是对于最后的几个错误同步的例子不明白为什么?
例如:
var a string
var done bool
func setup() {
a = "hello, world"
done = true
}
func main() {
go setup()
for !done {
}
print(a)
}
和前面一样,这里不保证在 main 中对 done 的写入的监测, 蕴含对 a 的写入也进行监测,因此该程序也可能会打印出一个空字符串。 更糟的是,由于在两个线程之间没有同步事件,因此无法保证对 done 的写入总能被 main 监测到。main 中的循环不保证一定能结束。
上面这段说明中说有可能会出问题,打印空字符之类的,但是我没想白,不是只有等done为true之后才会进行print嘛,那时候不是已经进行赋值了嘛? 另外用go1.11 尝试运行多次,结果也是可以输出hello world的
有疑问加站长微信联系(非本文作者)

好像不关内存什么事吧,是你基础没有学好
首先 done 的初始化应该为 false 就是 0,一般情况下 所有的语言 bool 类型 初始化都为 false
然后 for 循环,!done ,差不多相当于 只要是 done 为false 就会一直循环下去,直到 done 变为 true。
也就是 不关 你的 setup 什么时候执行,一旦执行后,done 变为 true,for 循环结束跳出,之后 print
你上面那个说明在哪里看到的?这里难道不是应该注意读写并发么
之后是 关于 为什么会输出 空字符,那么原因 很可能是,done 初始化的时候 初始化成 true了,这种情况虽然很少,但是也不排除 如果 你的程序 就只有 这些的话。
所以 保险的做法,done 赋初值
我是在内存模型的说明文档上看见的,放在最后作为错误的同步给出的例子以及说明。你提到的读写并发 能详细说明一下么?
你之前说的那部分 我都能明白。但是global bool 在没有赋值的情况下 不应该就是被赋为false的么 ,这是在规范里面写明的呀? 还有被初始化为true这种操作的么?
有呀,虽然一般情况下都会是 false ,但是 也有特别情况是 true,比如 编译器没有进行初始化处理,或者刚好指向的内存有值。所以为了安全起见 初始化的时候都会给一个 初值
这一个done变量,在setup协程写的同时在主线程读,一般用chan来控制同步。能给一下出处地址么?
http://docs.studygolang.com/ref/mem在最后,我知道应该用个什么机制 来控制一下同步。 其实我的问题是为什么那么做会出问题?
不应该出问题,你加读写锁,不会有问题的。golang语言本身就赋予了初始化值为false,如果还要自己去初始化那就不是go了。我跑了10000遍,没有问题,可以忽略。
所以 是说明错了? 我怎么 这么没信心呢。。。
这段代码应该有两种情况,你启动go程, 子go程和主程同时抢占时间轮片 ,1.子程抢到时间轮片,子程执行,done改变,输出helloword,然后主程执行,结束。 2.主程抢到时间轮片,执行for循环,再次竞争时间轮片,子程结束,主程结束,输出helloworld.应该都是输出helloworld. 也可能我才疏学浅,未能领悟精髓。。
a = "hello, world"
done = true
不能保证这两个在一个事务里面,done = true 时,a 可能还没赋值
那条for循环啥也没做会占用100%的cpu,需要加入条语句调度一下
还有@mlkr 13楼说的可能是对的,cpu可能会乱序执行,这里应该保护一下
for循环这样的写法类似内核态里的自旋锁,用户态不建议这么写,会空耗大量cpu(新版的go是不是已经会对执行中的for进行调度了?)
那么是否能理解成,哪怕是在一个goroutinue,编译器也是可能通过并发的执行操作进行优化,就像这里,a和done的赋值可能并没有按照先后顺序进行?似乎只有这种解释,能说明为什么会说有错。。。
你说的对 但是我刚开始学看的官方文档,然后就都尝试一下,不太理解。然后就拿出来请教一下大家
用了1.10.1 ,1.10.3, 1.11三个版本输出都是非空字符串。。没法重现你的问题啊唉。。。
不是说明错了,假如是同一个线程中出现cpu的这种时序错乱,能避免么?只能说编程的时候尽量让自己的代码规范一点,for循环这里最好用chan, 上面这种写法就不应该出现。<-done 这样写不是好很多?
这里是操作系统进程同步问题, 你贴出来的说明是没有问题的.
我似乎有点明白了 谢谢大家~~
别的不说,你的这个写法这是在考验cpu。
正常情况下,你的主留成和go起的协程可以理解为两个平行的代码空间。
所以需要通过Chan来进行互相沟通。
具体这里应该用select。
这个问题明明文档一开始就写了啊?go的内存模型就是这样的啊,文档例子里就有作者提的代码。The Go Memory Model 中说了
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.
大概意思就是不改变语言规范定义的行为时,编译器和处理器才可以重新排序在单个goroutine中执行的读取和写入。由于这种重新排序,一个goroutine观察到的执行顺序可能与另一个goroutine所感知的顺序不同。例如,如果一个goroutine执行a = 1; b = 2;,另一个可能会在a的更新值之前观察到b的更新值。
也就是说对于题主的例子:在main goroutine看来a和done的顺序是不确定的,如果编译器和cpu进行了重排,就有可能done比a先执行的。
@sheepbao 谢谢 我明白了
看 <Go语言高级编程> 看到这个例子, 里面有一段话: 在Go语言中,同一个Goroutine线程内部,顺序一致性内存模型是得到保证的。但是不同的Goroutine之间,并不满足顺序一致性内存模型,需要通过明确定义的同步事件来作为同步的参考。
因为你的机器是多核的,显示的设置成单核就会出现死循环了 runtime.GOMAXPROCS(1)
单核情况下,go程中setup函数中对done变量写入true之后,主程for循环中没有任何空时间去主存中读取done的值,就会造成死循环,可以在for循环体加上fmt.Print(),让出时间片就能读到主存中的值了
做了下实验,不是for循环没有读到主存中的值 而是for循环先于go程执行,然后一直处于死循环,没有机会执行go程中的代码了
0
go1.11无法复现