Channel
设计原理
不要通过共享内存的方式进行通信,而是应该通过通信的方式共享内存。
在主流编程语言中,多个线程传递数据的方式一般都是共享内存。
Go 可以使用共享内存加互斥锁进行通信,同时也提供了一种不同的并发模型,即通信顺序进程(Communicating sequential processes,CSP)。Goroutine 和 Channel 分别对应 CSP 中的实体和传递信息的媒介,Goroutine 之间会通过 Channel 传递数据。
上图中的两个 Goroutine,一个会向 Channel 中发送数据,另一个会从 Channel 中接收数据,它们两者能够独立运行并不存在直接关联,但是能通过 Channel 间接完成通信。
关闭 channel
func closechan(c *hchan) {// 关闭一个 nil channel,panicif c == nil {panic(plainError("close of nil channel"))}// 上锁lock(&c.lock)// 如果 channel 已经关闭if c.closed != 0 {unlock(&c.lock)// panicpanic(plainError("close of closed channel"))}// …………// 修改关闭状态c.closed = 1var glist *g// 将 channel 所有等待接收队列的里 sudog 释放for {// 从接收队列里出队一个 sudogsg := c.recvq.dequeue()// 出队完毕,跳出循环if sg == nil {break}// 如果 elem 不为空,说明此 receiver 未忽略接收数据// 给它赋一个相应类型的零值if sg.elem != nil {typedmemclr(c.elemtype, sg.elem)sg.elem = nil}if sg.releasetime != 0 {sg.releasetime = cputicks()}// 取出 goroutinegp := sg.ggp.param = nilif raceenabled {raceacquireg(gp, unsafe.Pointer(c))}// 相连,形成链表gp.schedlink.set(glist)glist = gp}// 将 channel 等待发送队列里的 sudog 释放// 如果存在,这些 goroutine 将会 panicfor {// 从发送队列里出队一个 sudogsg := c.sendq.dequeue()if sg == nil {break}// 发送者会 panicsg.elem = nilif sg.releasetime != 0 {sg.releasetime = cputicks()}gp := sg.ggp.param = nilif raceenabled {raceacquireg(gp, unsafe.Pointer(c))}// 形成链表gp.schedlink.set(glist)glist = gp}// 解锁unlock(&c.lock)// Ready all Gs now that we've dropped the channel lock.// 遍历链表for glist != nil {// 取最后一个gp := glist// 向前走一步,下一个唤醒的 gglist = glist.schedlink.ptr()gp.schedlink = 0// 唤醒相应 goroutinegoready(gp, 3)}
}
- 关闭状态 closed 值为 1。
- 将 recvq 和 sendq 中所有正在阻塞的 gorountine 唤醒。sender 直接 panic,receiver 返回一个相应类型的零值。
channel 的使用,有几点不方便的地方:
3. 在不改变 channel 自身状态的情况下,无法获知一个 channel 是否关闭。
4. 关闭一个 closed channel 会导致 panic。
5. 向一个 closed channel 发送数据会导致 panic。
由于关闭一个 cloed 的 channel 或者由 sender 关闭 channel 会导致panic,所以关闭 channel 应该是:
6. channel 不手动关闭由 gc 代劳。
7. 由 sender 关闭并确保只关闭一次(比如 sync.Once)。
channel 发送和接收元素的本质
All transfer of value on the go channels happens with the copy of value.
channel 的发送和接收操作本质上都是值的拷贝。无论是从 sender goroutine 的栈到 chan buf,还是从 chan buf 到 receiver goroutine,或者是直接从 sender goroutine 到 receiver goroutine。
channel 在什么情况下会引起资源泄漏
channel 可能会引发 goroutine 泄漏。
泄漏的原因是 goroutine 操作 channel 后,处于发送或接收阻塞状态,而 channel 处于满或空的状态,一直得不到改变。同时,垃圾回收器也不会回收此类资源,进而导致 gouroutine 会一直处于等待队列中。
程序运行过程中,对于一个 channel,如果没有任何 goroutine 引用了,gc 会对其进行回收操作,不会引起内存泄漏。