共计 2377 个字符,预计需要花费 6 分钟才能阅读完成。
面试题
这是 Go Quiz 系列的第 2 篇,对于 channel
和select
的个性。
这道题比较简单,然而通过这道题能够加深咱们对 channel
和select
的了解。
package main
func main() {c := make(chan int, 1)
for done := false; !done; {
select {
default:
print(1)
done = true
case <-c:
print(2)
c = nil
case c <- 1:
print(3)
}
}
}
- A: 321
- B: 21
- C: 1
- D: 31
这道题次要考查以下知识点:
channel
的数据收发在什么状况会阻塞?select
的运行机制是怎么的?nil channel
收发数据是什么后果?
解析
- 对于无缓冲区的
channel
,往channel
发送数据和从channel
接收数据都会阻塞。 -
对于
nil channel
和有缓冲区的channel
,收发数据的机制如下表所示:channel nil 空的 非空非满 满了 往 channel 发送数据 阻塞 发送胜利 发送胜利 阻塞 从 channel 接收数据 阻塞 阻塞 接管胜利 接管胜利 敞开 channel panic 敞开胜利 敞开胜利 敞开胜利 -
channel
被敞开后:- 往被敞开的
channel
发送数据会触发 panic。 - 从被敞开的
channel
接收数据,会先读完channel
里的数据。如果数据读完了,持续从channel
读数据会拿到channel
里存储的元素类型的零值。 channel
被敞开后,如果再次敞开,会引发 panic。
- 往被敞开的
-
select
的运行机制如下:- 选取一个可执行不阻塞的
case
分支,如果多个case
分支都不阻塞,会随机算一个case
分支执行,和case
分支在代码里写的程序没关系。 - 如果所有
case
分支都阻塞,会进入default
分支执行。 - 如果没有
default
分支,那select
会阻塞,直到有一个case
分支不阻塞。
- 选取一个可执行不阻塞的
依据以上规定,本文最开始的题目,在运行的时候
- 第 1 次 for 循环,只有
c <- 1
是不阻塞的,所以最初一个case
分支执行,打印 3。 - 第 2 次 for 循环,只有
<-c
是不阻塞的,所以第 1 个case
分支执行,打印 2,同时channel
被赋值为nil
。 - 第 3 次 for 循环,因为
channel
是nil
,对 nil channel
的读和写都阻塞,所以进入default
分支,打印 1,done
设置为true
,for 循环退出,程序运行完结。
因而打印后果是321
, 答案是A
。
加餐:channel 函数传参是援用传递?
网上有些文章写 Go 的 slice
,map
,channel
作为函数参数是传援用,这是 谬误 的,Go 语言里只有值传递,没有援用传递。
既然 channel
是值传递,那为什么 channel
作为函数形参时,函数外部对 channel
的读写对外部的实参 channel
是可见的呢?
对于这个问题,看 Go 的源码就高深莫测了。channel
定义在 src/runtime/chan.go
第 33 行,源码地址:https://github.com/golang/go/…
func makechan(t *chantype, size int) *hchan
// channel 构造体
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
咱们通过 make
函数来创立 channel
时,Go 会调用运行时的 makechan
函数。
从下面的代码能够看出 makechan
返回的是指向 channel
的指针。
因而 channel
作为函数参数时,实参 channel
和形参 channel
都指向同一个 channel
构造体的内存空间,所以在函数外部对 channel
形参的批改对外部 channel
实参是可见的,反之亦然。
开源地址
文章和示例代码开源地址在 GitHub: https://github.com/jincheng9/…
公众号:coding 进阶
集体网站:https://jincheng9.github.io/
知乎:https://www.zhihu.com/people/…
好文举荐
- 被 defer 的函数肯定会执行么?
- Go 有援用变量和援用传递么?map,channel 和 slice 作为函数参数是援用传递么?
- new 和 make 的应用区别是什么?
- 一文读懂 Go 匿名构造体的应用场景
- 官网教程:Go 泛型入门
- 一文读懂 Go 泛型设计和应用场景
- Go Quiz: 从 Go 面试题看 slice 的底层原理和注意事项
References
- https://go101.org/quizzes/cha…
- https://jincheng9.github.io/
- https://github.com/jincheng9/…
- https://github.com/jincheng9/…