题目序号:2030
题目来源:字节
频次:1
答案:栾龙生
defer
defer语句用来延时函数的调用,常用于关闭文件描述符、释放锁等资源释放场景。
defer语句采用后进先出的设计,类似于栈的方式,函数执行时每遇到一个defer都会把一个函数压入栈中,函数返回前再将函数从栈中取出执行,最早被压入栈中的函数最晚被执行。
不仅函数正常返回会执行被defer延迟的函数,函数中任意一个return语句、panic语句均会触发延迟函数。
使用场景
释放资源
m.mutex.Lock() defer ,.mutex.Unlock()
流程控制
var wg wait.Group(); defer wg.Wait()
异常处理
defer也常用于处理异常,与recover配合可以消除panic。
defer func() {recover()}()
行为规则
延迟函数的参数在defer语句出现时就已经确定
func a() { i := 0 defer fmt.Println(i) i++ return }
defer语句中的fmt.Println()参数i值在defer出现时就已经确定了,实际上是复制了一份。后面对变量i的修改不会影响fmt.Println()函数的执行,仍然打印0。对于传递指针类型参数,延迟函数的参数是一个地址值,在这种情况下,defer后面的语句对变量的修改可能会影响延迟函数。
- 延迟函数按后进先出的顺序执行,即先出现的defer最后执行
延迟函数可能操作主函数的具名返回值
关键字return不是一个原子操作,实际上分两步执行。比如 return i,第一步将i值存入栈中作为返回值,第二步执行跳转,而defer函数的执行时机正是在跳转前,所以defer函数还是有机会操作返回值的。
func b() (res int) { i := 1 defer func() { res++ }() return i } //output: 2
chan
管道是go在语言层面上提供的携程间的通信方式。
声明和初始化管道
变量声明
var ch chan int
使用内置函数make
ch1 := make(chan int) //无缓冲管 ch2 := make(chan int, 5) //有缓冲管道
管道操作
操作符
操作符
<-
代表数据流向,管道在左表示向管道写入数据,管道在右表示从管道中读取数据ch := make(chan int, 10) ch <- 1 //写数据 d := <- ch //读数据
数据读写
管道没有缓冲区时,从管道中读取数据会阻塞,直到有协程向管道中写入数据。同样,向管道中写入数据也会阻塞,直到有协程从管道中读取数据。
管道中有缓冲区没有数据时,读数据也会阻塞,直到有协程写入数据。向管道中写数据,如果缓冲区已满,那么也会阻塞,直到有协程从缓冲区读数据。
对于值为nil的管道,无论读写都会阻塞,而且是永久阻塞。
使用内置函数close()可以关闭管道,尝试向关闭的管道写入数据会触发panic,但关闭的管道仍可读。
管道表达式最多给两个变量赋值
v1 := <-ch x, ok := <-ch
第一个变量为读出的数据,第二个变量表示是否成功的读取了数据。