return
结束当前函数,并返回指定值,defer调用会执行;
这里需要引申出来一个知识点–return和defer的执行顺序问题,先看一段代码:
1 | package main |
输出:
1 | f12: 1001 |
原因:
- 多个defer的执行顺序为“后进先出”
- defer、return、返回值三者的执行逻辑应该是:
return最先执行,return负责将结果写入返回值中
接着defer开始执行一些收尾工作
最后函数携带当前返回值退出 - 如果函数的返回值是无名的(不带命名返回值,如上面例子中的
f1
)
则go语言会在执行return的时候会执行一个类似创建一个临时变量作为保存return值的动作 - 而有名返回值的函数(如上面例子中的
f2
),由于返回值在函数定义的时候已经将该变量进行定义,
在执行return的时候会先执行返回值保存操作,而后续的defer函数会改变这个返回值,
但是由于使用的函数定义的变量,所以执行defer操作后对该变量的修改会影响到return的值
针对后面两条,我们可以通过go tool compile
工具来进行验证:
1 | go tool compile -S -N -l main.go > main.s // -N -l两个命令行选项用于关闭Go编译器的优化,优化后的代码会掩盖实现细节 |
查看main.s
文件,重点查看一下几行:
1 | 0x00af 00175 (main.go:23) MOVQ $1000, "".i+8(SP) |
1 | 0x009e 00158 (main.go:38) MOVQ $1000, "".i+136(SP) |
代码解读:
为了弄清上述两种情况的区别,我们首先要理解return 返回值的运行机制:
return 并非原子操作,分为赋值,和返回值两步操作
f1 : 实际上return 执行了两步操作,因为返回值没有命名,
所以return 默认指定了一个临时变量(假设为s),首先将i赋值给s,
后续的操作因为是针对i,进行的,所以不会影响s, 此后因为s不会更新,
所以return s 不会改变,相当于:
var i int
s := i
return s
f2 : 同上,s 就相当于命名的变量i, 因为所有的操作都是基于命名变量i(s),返回值也是i,
所以每一次defer操作,都会更新返回值i
runtime.Goexit()
结束当前goroutine
,其他的goroutine
不受影响,主程序也一样继续运行,defer
会被调用;
如果在main
函数中调用,则main goroutine
终止,由于main
函数没有return
,则其他groutine
会继续执行;(main groutine
正常退出,则其他groutine
都会退出)
1 | // 源码注释在此 |
os.Exit(code int)
会结束当前程序,不管你三七二十一。几乎不会用到
1 | // 源码注释在此 |
panic(v interface{})
停止当前goroutine
的正常执行,defer
会执行,panic
会一直向上传递.
1 | // 源码注释在此 |
结尾
多看源码,多实验,好记性不如烂笔头。