面试题

这是Go Quiz系列的第2篇,对于channelselect的个性。

这道题比较简单,然而通过这道题能够加深咱们对channelselect的了解。

package mainfunc 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收发数据是什么后果?

解析

  1. 对于无缓冲区的channel,往channel发送数据和从channel接收数据都会阻塞。
  2. 对于nil channel和有缓冲区的channel,收发数据的机制如下表所示:

    channelnil空的非空非满满了
    往channel发送数据阻塞发送胜利发送胜利阻塞
    从channel接收数据阻塞阻塞接管胜利接管胜利
    敞开channelpanic敞开胜利敞开胜利敞开胜利
  3. channel被敞开后:

    • 往被敞开的channel发送数据会触发panic。
    • 从被敞开的channel接收数据,会先读完channel里的数据。如果数据读完了,持续从channel读数据会拿到channel里存储的元素类型的零值。
    • channel被敞开后,如果再次敞开,会引发panic。
  4. 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循环,因为channelnil对nil channel的读和写都阻塞,所以进入default分支,打印1,done设置为true,for循环退出,程序运行完结。

因而打印后果是321,答案是A

加餐:channel函数传参是援用传递?

网上有些文章写Go的slicemapchannel作为函数参数是传援用,这是谬误的,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/...

好文举荐

  1. 被defer的函数肯定会执行么?
  2. Go有援用变量和援用传递么?map,channel和slice作为函数参数是援用传递么?
  3. new和make的应用区别是什么?
  4. 一文读懂Go匿名构造体的应用场景
  5. 官网教程:Go泛型入门
  6. 一文读懂Go泛型设计和应用场景
  7. Go Quiz: 从Go面试题看slice的底层原理和注意事项

References

  • https://go101.org/quizzes/cha...
  • https://jincheng9.github.io/
  • https://github.com/jincheng9/...
  • https://github.com/jincheng9/...