说一说go的defer和chan
参考解析
题目来源:字节
答案:
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
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`
第一个变量为读出的数据,第二个变量表示是否成功的读取了数据。