问一个并发程序可见性的问题, golang 语言

345 天前
 rockyliang

Go 官网有一段代码例子:

var c = make(chan int)
var a string

func f() {
	a = "hello, world"
	<-c
}

func main() {
	go f()
	c <- 0
	print(a)
}

官网说使用了 channel 后,这段代码可以确保能正常打印出"hello, world",原因是什么?

这是我的理解(有可能不对,欢迎指正): 假设 [ f 协程] 运行在 cpu0 核心上, [ main 协程] 运行在 cpu1 核心上, [ f 协程] 修改完 a 变量后,由于不同 cpu 核心之间存在缓存一致性问题,这个修改对于 [ main 协程] 来说有可能是不可见的,也就是 [ main 协程] 有可能会打印出一个空字符串

那么,channel 在这段代码里发挥的作用是什么,它是怎么确保 [ main 协程] 可以正常打印出"hello, world"的呢?

11783 次点击
所在节点    Go 编程语言
111 条回复
cyrivlclth
345 天前
@yph007595 我也觉得#5 说的怪怪的,这像是某个其他语言的特性?搜了下,只能搜到 java 的 volatile 。
lifei6671
345 天前
@ZField 应该不是,明显讨论的是多核 CPU 下,某个核心修改了变量,是如何将变量同步到全部核心缓存的问题。按照 op 的理解,cpu0 修改了 a 变量,必须有一种机制将 a 变量的值同步到其他 cpu 缓存,否则其他 cpu 核心读到的还是旧变量。
pkoukk
345 天前
你用过带 await 语法的语言么?
这相当于 await f(),同步等待这个异步函数执行完成
rockyliang
345 天前
@cyrivlclth #21 所以可见性问题只存在于 Java 语言吗
pkoukk
345 天前
另外,不要用共享内存去理解 channel ,channel 的设计是有顺序的
https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-channel/
xiaxiaocao
345 天前
楼里没听说过的可以 Google 下 The Go Memory Model ,官方有文章讲述的,和 Java 的内存模型也比较类似。
无缓冲的 chan 的入队和出队语义上是保证 happens before 关系的。具体到实现里面可能是有个锁/条件变量,也可能是其他的机制,比如在同一个 P 上调度的时候可能就不需要锁。
xiaxiaocao
345 天前
@rockyliang 可见性问题存在于所有语言,只是有些人不知道就以为不存在
ZField
345 天前
@lifei6671 #22 = =我提到的就是这个意思,java 里面 JMM 模型中的 volatile 关键字就是做这件事的,强制同步,不然就会出现他所提到的可见性的问题。所以我才会猜测他的想法是基于 jvm
xiaxiaocao
345 天前
@rockyliang 可见性问题存在于所有语言,只是有些人不知道就以为不存在
@xiaxiaocao 啊,我这里说的不对,一些单线程的语言或者只能用单核的语言应该就没有可见性问题
yph007595
345 天前
@rockyliang #20 @cyrivlclth 不了解 java ,但 go 中没有 volatile 这个关键字,我认为 go 会帮我们处理好这其中的关系。即:如果一个共享变量 a, 两个线程 t1, t2, t1 肯定先运行,修改了 a, t2 后运行,那么 t2 读取的 a 肯定是修改后的。如果这不能保证,那我感觉我以前写的程序的运行的基石要坍塌了。。
raynor2011
345 天前
chan, atomic,mutex 底层已经解决了可见性问题,所以 golang 一般不关心可见性问题,可以参考官方文档 https://go.dev/ref/mem
cyrivlclth
345 天前
@rockyliang
@xiaxiaocao 不是,可见性问题是都有的。

就像楼上说的,可见性你看 go 语言的内存管理就行。channel 的作用,你看 https://github.com/golang/go/blob/41d8e61a6b9d8f9db912626eb2bbc535e929fefc/src/runtime/chan.go#L32 这里就行,有互斥锁的
rockyliang
345 天前
@ZField #28 对,java 的 volatile 关键字,我看网上资料说是通过禁用 CPU 缓存来解决可见性问题的,但 golang 的 channel 是不是通过同样的方式去解决的就不知道了,还是说可见性问题只存在 java ?但这个貌似也说不通,只要是多线程并发,应该就会有可见性问题
charslee013
345 天前
> 因为 golang 是多线程,多线程并发就会有可见性问题,一个线程修改了共享变量,这个修改对于其它线程来说不一定能观测到

不要被 Java 多线程带偏了,Golang 是多协程而不是多线程,Golang 中唯一使用多线程场景是 GMP 中的 M 用来跟内核的线程进行绑定

这里首先是 `a = "hello, world"` 执行后,主协程中的 `c <- 0` 阻塞到 f() 协程执行到 `<-c` 后才会继续下去,此刻无论如何 a 变量已经被安全的修改了
xiaxiaocao
345 天前
@yph007595 go 里需要用 atomic.Load/atomic.Store....
当然不用程序常常还是能正常跑的,因为要出现可见问题的几率有点低
xiaxiaocao
345 天前
@rockyliang volatile 只是一种方式。基本上同步操作,比如锁、信号量、线程 start/join 、阻塞队列入队/出队,语义上都是保证 happends before 关系,这一点 java 和 go ,和其他语言都是类似的
rockyliang
345 天前
@raynor2011
@cyrivlclth

非常感谢,我先消化下你们发出来的链接内容
pkoukk
345 天前
@rockyliang #5 channel 有锁啊,有锁还不能解决可见性问题么?那你用的 java 是通过啥解决的
chaleaochexist
345 天前
不同 cpu 核心之间存在缓存一致性问题

这个问题也不需要咱们考虑啊.
那你用别的语言写并发程序也需要考虑这个问题了.

我唯一能提示给你的是, channel 是一个有锁的队列.
cyrivlclth
345 天前
@yph007595 啊,你这里说的先后,这段代码就是用阻塞的 channel 去保证这个执行的先后顺序的,如果没有这个 channel ,那就不能保证这两个协程谁先执行哪里了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/999936

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX