背景
Go内置的数据结构channel置信大家都很相熟,channel在Go语言里表演了十分重要的角色。
- channel能够帮忙实现goroutine之间的通信和同步。
- Go语言通过goroutine和channel实现了CSP(Communicating Sequencial Process)模型。
Go语言的发明人之一Robe Pike说过上面这句话:
Don't communicate by sharing memory, share memory by communicating.
这句话里暗藏的含意其实是心愿Go开发者应用channel来实现goroutine之间的通信。
channel分为2种类型:
- bi-directional channel(双向channel):既能够往channel里发送数据,也能够从channel接收数据
uni-directional channel(单向channel)
- send-only channel:只能往channel发送数据,不能从channel接收数据,否则会编译报错
- receive-only channel:只能从channel接收数据,不能往channel发送数据,否则会编译报错
单向channel的一个典型应用场景是作为函数或办法参数,用来管制只能往channel发送数据或者只能从channel接收数据,防止误操作。
package mainimport ( "fmt")// send-only channelfunc testSendChan(c chan<- int) { c <- 20}// receive-only channelfunc testRecvChan(c <-chan int) { result := <-c fmt.Println("result:", result)}func main() { ch := make(chan int, 3) testSendChan(ch) testRecvChan(ch)}
比方下面这段程序,testSendChan
的参数是一个send-only channel,testRecvChan
的参数是一个receive-only channel。
实参ch
是一个双向channel,Go runtime会把双向channel转换成单向channel传给对应的函数。
问题
下面的例子是单向channel作为函数形参,双向channel作为函数实参,make函数创立的是一个双向channel。
那咱们能够用make来创立单向channel么?make创立的单向channel有理论应用场景么?有潜在的坑么?
咱们先看看如下2道题目:
题目1
ch := make(<-chan int)close(ch)fmt.Println("ok")
- A: 打印ok
- B: 运行时报错:fatal error - deadlock
- C: 运行时报错:painic
- D: 编译失败
题目2
c := make(chan<- int)close(c)fmt.Println("ok")
- A: 打印ok
- B: 运行时报错:fatal error - deadlock
- C: 运行时报错:painic
- D: 编译失败
大家能够先略微进展一下,想想这2道题的答案会是什么。
解析
答案
题目1的答案是D,题目1创立了一个receive-only channel,只能从channel里接管值,不能往channel里发送值。
对于receive-only channel不能close,如果做close操作,会有如下报错:
./main.go:9:7: invalid operation: close(ch) (cannot close receive-only channel)
题目2的答案是A,题目2创立了一个send-only channel,只能往channel里发送值,不能从channel里接管值。
对于send-only channel能够失常close。
为什么receive-only channel不能close,然而send-only channel能够close呢?
这是因为receive-only channel示意只能从这个channel里接收数据,应用方对这个channel只有读权限,对channel没有写权限,即不能往channel发送数据或者对channel做close操作。
所以题目1的答案是D,题目2的答案是A。
衍生问题
// send-only channelfunc testSendChan(c chan<- int) { c <- 20}// receive-only channelfunc testRecvChan(c <-chan int) { result := <-c fmt.Println("result:", result)}func main() { ch := make(chan int, 3) testSendChan(ch) testRecvChan(ch)}
下面这段代码是单向channel的典型应用场景,函数实参是双向channel,函数参数是单向channel,咱们能够通过send-only channel往channel发送数据,通过receive-only channel从channel里接收数据,这个很好了解。然而要留神的是这里的函数实参是双向channel。
仔细的开发者可能有个疑难,对于下面的2道题目,make创立的是单向channel,要么只能往这个channel里发数据,要么只能从这个channel接收数据,而不像下面代码里make创立的channel是个双向channel。
问题来了,对于make创立的单向channel,有理论用处么?
比方make(chan<- int)
创立的send-only channel,只能往这个channel发数据,没方法从这个channel里读数据,有啥用么?
比方make(<-chan int)
创立的receive-only channel,只能从这个channel里收数据,没方法往这个channel里发数据,有啥用么?
对于这个问题,其实Go语言的大佬之间也产生过比拟大的争议。
Golang团队的Brad Fitzpatrick就埋怨过,认为编译器应该间接对make创立单向channel报错,因为make创立的单向channel没啥用。
I'm surprised the compiler permits create send- or receive-only channels:------package mainimport "fmt"func main() { c := make(<-chan int) fmt.Printf("%T\n", c)}------Is that an accident?I can't see what utility they'd have.
Go语言负责人Russ Cox给的反馈是:认为没有明确理由让编译器禁止make创立单向channel,因为对单向channel如果有非法的操作,编译器还是会报错,而且让编译器间接禁止make创立单向channel会减少Go设计的复杂性,没有必要,得失相当。
I can't see a reason to disallow it either though.All I am saying is that explicitly disallowing it addscomplexity to the spec that seems unnecessary.What bugs or problems does it avoid to make thisspecial exclusion in the rules for make?Russ
举荐浏览
- Go Quiz: 从Go面试题看channel的注意事项
- Go Quiz: 从Go面试题看channel在select场景下的注意事项
总结
- 对于无缓冲区的
channel
,往channel
发送数据和从channel
接收数据都会阻塞。 对于
nil channel
和有缓冲区的channel
,收发数据的机制如下表所示:channel nil 空的 非空非满 满了 往channel发送数据 阻塞 发送胜利 发送胜利 阻塞 从channel接收数据 阻塞 阻塞 接管胜利 接管胜利 敞开channel panic 敞开胜利 敞开胜利 敞开胜利 channel
被敞开后:- 往被敞开的
channel
发送数据会触发panic。 从被敞开的
channel
接收数据,会先读完channel
里的数据。如果数据读完了,持续从channel
读数据会拿到channel
里存储的元素类型的零值。data, ok := <- c
对于下面的代码,如果channel
c
敞开了,持续从c
里读数据,当c
里还有数据时,data
就是对应读到的值,ok
的值是true
。如果c
的数据曾经读完了,那data
就是零值,ok
的值是false
。channel
被敞开后,如果再次敞开,会引发panic。
- 往被敞开的
select
的运行机制如下:- 选取一个可执行不阻塞的
case
分支,如果多个case
分支都不阻塞,会随机选一个case
分支执行,和case
分支在代码里写的程序没关系。 - 如果所有
case
分支都阻塞,会进入default
分支执行。 - 如果没有
default
分支,那select
会阻塞,直到有一个case
分支不阻塞。
- 选取一个可执行不阻塞的
开源地址
文章和示例代码开源在GitHub: Go语言高级、中级和高级教程。
公众号:coding进阶。关注公众号能够获取最新Go面试题和技术栈。
集体网站:Jincheng's Blog。
知乎:无忌。
福利
我为大家整顿了一份后端开发学习材料礼包,蕴含编程语言入门到进阶常识(Go、C++、Python)、后端开发技术栈、面试题等。
关注公众号「coding进阶」,发送音讯 backend 支付材料礼包,这份材料会不定期更新,退出我感觉有价值的材料。还能够发送音讯「进群」,和同行一起交流学习,答疑解惑。
References
- https://twitter.com/val_delep...
- https://github.com/golang/go/...