说一说go的defer和chan

参考解析

题目来源:字节

答案:

  1. defer

    defer语句用来延时函数的调用,常用于关闭文件描述符、释放锁等资源释放场景。

    defer语句采用后进先出的设计,类似于栈的方式,函数执行时每遇到一个defer都会把一个函数压入栈中,函数返回前再将函数从栈中取出执行,最早被压入栈中的函数最晚被执行。

    不仅函数正常返回会执行被defer延迟的函数,函数中任意一个return语句、panic语句均会触发延迟函数。

    使用场景

    • 释放资源

      1. m.mutex.Lock()
      2. defer ,.mutex.Unlock()
    • 流程控制

      1. var wg wait.Group();
      2. defer wg.Wait()
    • 异常处理

      defer也常用于处理异常,与recover配合可以消除panic。

      defer func() {recover()}()

  1. **行为规则**
  2. * 延迟函数的参数在defer语句出现时就已经确定
  3. ```
  4. func a() {
  5. i := 0
  6. defer fmt.Println(i)
  7. i++
  8. return

}

  1. defer语句中的fmt.Println()参数i值在defer出现时就已经确定了,实际上是复制了一份。后面对变量i的修改不会影响fmt.Println()函数的执行,仍然打印0。对于传递指针类型参数,延迟函数的参数是一个地址值,在这种情况下,defer后面的语句对变量的修改可能会影响延迟函数。
  2. * 延迟函数按后进先出的顺序执行,即先出现的defer最后执行
  3. * 延迟函数可能操作主函数的具名返回值
  4. 关键字return不是一个原子操作,实际上分两步执行。比如 return i,第一步将i值存入栈中作为返回值,第二步执行跳转,而defer函数的执行时机正是在跳转前,所以defer函数还是有机会操作返回值的。
  1. func b() (res int) {
  2. i := 1
  3. defer func() {
  4. res++
  5. }()
  6. return i

} //output: 2

  1. 2. **chan**
  2. 管道是go在语言层面上提供的携程间的通信方式。
  3. **声明和初始化管道**
  4. * 变量声明
  5. `var ch chan int`
  6. * 使用内置函数make
  1. ch1 := make(chan int) //无缓冲管

ch2 := make(chan int, 5) //有缓冲管道 ```

  1. **管道操作**
  2. * 操作符
  3. 操作符`<-`代表数据流向,管道在左表示向管道写入数据,管道在右表示从管道中读取数据
  4. `ch := make(chan int, 10) ch <- 1 //写数据 d := <- ch //读数据`
  5. * 数据读写
  6. 管道没有缓冲区时,从管道中读取数据会阻塞,直到有协程向管道中写入数据。同样,向管道中写入数据也会阻塞,直到有协程从管道中读取数据。
  7. 管道中有缓冲区没有数据时,读数据也会阻塞,直到有协程写入数据。向管道中写数据,如果缓冲区已满,那么也会阻塞,直到有协程从缓冲区读数据。
  8. 对于值为nil的管道,无论读写都会阻塞,而且是永久阻塞。
  9. 使用内置函数close()可以关闭管道,尝试向关闭的管道写入数据会触发panic,但关闭的管道仍可读。
  10. 管道表达式最多给两个变量赋值
  11. `v1 := <-ch x, ok := <-ch`
  12. 第一个变量为读出的数据,第二个变量表示是否成功的读取了数据。