关于golang:Go-Quiz-从Go面试题看channel的注意事项

39次阅读

共计 2377 个字符,预计需要花费 6 分钟才能阅读完成。

面试题

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

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

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

解析

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

    channel nil 空的 非空非满 满了
    往 channel 发送数据 阻塞 发送胜利 发送胜利 阻塞
    从 channel 接收数据 阻塞 阻塞 接管胜利 接管胜利
    敞开 channel panic 敞开胜利 敞开胜利 敞开胜利
  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/…

正文完
 0