Go并发的两大基石:goroutine && channel
谈谈CSP?
CSP,comunicating sequential processes,定义了用于进程通信的输入输出语句(channel的原型),指出应该重视输入输出和并发编程。
channel
作用
- 在并发编程中,生产者-消费者问题一直是个重点,它就可以实现这两者的解耦,让各自专注于工作。
- 与之类似的还有对并发数的控制
- 停止信号
- 执行定时任务或者超时就结束
性质
建通道生成的是一个指针,所以在函数间能直接传递channel本身
发送
接收
- 类型
一种返回是否接收成功的信息;一种没有,只是接收错误时接收者会接收到默认的零值(无法判断是传过来的数据为零值还是因为错误)
两种各有用处。
我个人认为:当不需要确保接收成功(或者说不需要接收的结果的时候可以用第二种)
两种方式都是调用chanrecv():
第一个bool表示通道是否阻塞/关闭;第二个bool表示是否成功接收到数据(通道中是否存在数据)
对已经关闭的channel进行读写会如何?
读
能一直读到内容,但是读到的内容根据通道内关闭前是否有元素而不同。
如果 channel 关闭前,buffer 内有元素还未读,会正确读到 chan 内的值,且返回的第二个 bool 值为 true;
如果 chan 关闭前,buffer 内有元素已经被读完,chan 内无值,返回 channel 元素的零值,第二个 bool 值为 false。
写
写已经关闭的 chan 会 panic。
收发数据的本质?
the copy of value
通道关闭的过程发生了什么
(源码有点长,我直接贴代码+注释一下)
func closechan(c *hchan) {
//如果channel为空,抛出panic
if c == nil {
panic(plainError("close of nil channel"))
}
//加锁
lock(&c.lock)
//如果channel已经关闭,抛出panic
if c.closed != 0 {
unlock(&c.lock)
panic(plainError("close of closed channel"))
}
if raceenabled {
callerpc := getcallerpc()
racewritepc(c.raceaddr(), callerpc, abi.FuncPCABIInternal(closechan))
racerelease(c.raceaddr())
}
c.closed = 1
//把挂在channel上的sender和receiver全连成一个链表
var glist gList
// release all readers
for {
sg := c.recvq.dequeue()
if sg == nil {
break
}
if sg.elem != nil {
typedmemclr(c.elemtype, sg.elem)
sg.elem = nil
}
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
gp := sg.g
gp.param = unsafe.Pointer(sg)
sg.success = false
if raceenabled {
raceacquireg(gp, c.raceaddr())
}
glist.push(gp)
}
// release all writers (they will panic)
for {
sg := c.sendq.dequeue()
if sg == nil {
break
}
sg.elem = nil
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
gp := sg.g
gp.param = unsafe.Pointer(sg)
sg.success = false
if raceenabled {
raceacquireg(gp, c.raceaddr())
}
glist.push(gp)
}
//一直到这里才解锁
unlock(&c.lock)
// Ready all Gs now that we've dropped the channel lock.
//唤醒链表中的元素
for !glist.empty() {
gp := glist.pop()
gp.schedlink = 0
goready(gp, 3)
}
}
- 之后,sender检测到channel已关闭,发生panic;
- receiver进行扫尾工作后返回
正确关闭通道的原则
本质:不要关闭或者发送数据到已关闭的通道
表现:不要关闭有多个并发发送者使用的通道;不要从接收端关闭通道
通道会引发协程资源泄露吗?原因?
会。当有协程操作通道的时候,gc不会回收此类资源,但有可能发生协程在发送或接收的阻塞态,而通道一直满或空的情况。
参考资料
- Go源码
- 《Go程序员面试笔试宝典》
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情