Go语言哲学 go语言书籍推荐知乎( 二 )


channel是可以让一个 goroutine 发送特定值到另一个 gouroutine 的通信机制 。
原生的 channel 是没有缓存的(unbuffered channel),可以用于 goroutine 之间实现同步 。
关闭后不能再写入 , 可以读取直到 channel 中再没有数据 , 并返回元素类型的零值 。
gopl/ch3/netcat3
首先从 channel 是怎么被创建的开始:
在 heap 上分配一个 hchan 类型的对象,并将其初始化,然后返回一个指向这个 hchan 对象的指针 。
理解Go语言哲学了 channel 的数据结构实现,现在转到 channel 的两个最基本方法:sends和receivces,看一下以上的特性是如何体现在sends和receives中的:
假设发送方先启动,执行 ch - task0 :
如此为 channel 带来Go语言哲学了goroutine-safe 的特性 。
在这样的模型里, sender goroutine - channel - receiver goroutine之间, hchan 是唯一的共享内存,而这个唯一的共享内存又通过 mutex 来确保 goroutine-safe,所有在队列中的内容都只是副本 。
这便是著名的 golang 并发原则的体现:
发送方 goroutine 会阻塞,暂停,并在收到 receive 后才恢复 。
goroutine 是一种 用户态线程 , 由 Go runtime 创建并管理 , 而不是操作系统,比起操作系统线程来说,goroutine更加轻量 。
Go runtime scheduler 负责将 goroutine 调度到操作系统线程上 。
runtime scheduler 怎么将 goroutine 调度到操作系统线程上Go语言哲学?
当阻塞发生时,一次 goroutine 上下文切换的全过程:
然而,被阻塞的 goroutine 怎么恢复过来?
阻塞发生时,调用 runtime sheduler 执行 gopark 之前,G1 会创建一个 sudog,并将它存放在 hchan 的 sendq 中 。sudog 中便记录了即将被阻塞的 goroutineG1,以及它要发送的数据元素 task4 等等 。
接收方 将通过这个 sudog 来恢复 G1
接收方 G2 接收数据, 并发出一个 receivce ,将 G1 置为runnable :
同样的, 接收方 G2 会被阻塞,G2 会创建 sudoq,存放在 recvq,基本过程和发送方阻塞一样 。
不同的是,发送方 G1如何恢复接收方 G2,这是一个非常神奇的实现 。
理论上可以将 task 入队 , 然后恢复 G2, 但恢复 G2后,G2会做什么呢?
G2会将队列中的 task 复制出来,放到自己的 memory 中,基于这个思路,G1在这个时候,直接将 task 写到 G2的 stack memory 中Go语言哲学!
这是违反常规的操作 , 理论上 goroutine 之间的 stack 是相互独立的,只有在运行时可以执行这样的操作 。
这么做纯粹是出于性能优化的考虑,原来的步骤是:
优化后,相当于减少了 G2 获取锁并且执行 memcopy 的性能消耗 。
channel 设计背后的思想可以理解为 simplicity 和 performance 之间权衡抉择 , 具体如下:
queue with a lock prefered to lock-free implementation:
比起完全 lock-free 的实现,使用锁的队列实现更简单 , 容易实现
驳狗屎文 "我为什么放弃Go语言此篇文章流传甚广, 其实里面没啥干货 ,  而且里面很多观点是有问题的. 这个文章在 golang-china 很早就讨论过了.
最近因为 Rust 1.0 和 1.1 的发布, 导致这个文章又出来毒害读者.
所以写了这篇反驳文章, 指出其中的问题.
有好几次,当我想起来的时候,总是会问自己:我为什么要放弃Go语言?这个决定是正确的吗?是明智和理性的吗?其实我一直在认真思考这个问题 。
开门见山地说,我当初放弃Go语言(golang),就是因为两个“不爽”:第一,对Go语言本身不爽;第二 , 对Go语言社区里的某些人不爽 。毫无疑问,这是非常主观的结论 。但是我有足够详实的客观的论据,用以支撑这个看似主观的结论 。

推荐阅读