golang的几种协程终止方法和区别


return

结束当前函数,并返回指定值,defer调用会执行;

这里需要引申出来一个知识点–return和defer的执行顺序问题,先看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import "fmt"

func main() {
fmt.Println("f1 result: ", f1())
fmt.Println("------")
fmt.Println("f2 result: ", f2())
}

func f1() int {
var i int
defer func() {
i++
fmt.Println("f11: ", i)
}()

defer func() {
i++
fmt.Println("f12: ", i)
}()

i = 1000
return i
}

func f2() (i int) {
defer func() {
i++
fmt.Println("f21: ", i)
}()

defer func() {
i++
fmt.Println("f22: ", i)
}()

i = 1000
return i
}

输出:

1
2
3
4
5
6
7
f12:  1001
f11: 1002
f1 result: 1000
------
f22: 1001
f21: 1002
f2 result: 1002

原因:

  • 多个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
2
3
4
5
6
7
0x00af 00175 (main.go:23)	MOVQ	$1000, "".i+8(SP)
0x00b8 00184 (main.go:24) MOVQ $1000, "".~r0+144(SP) // 这里就是上面所说的创建一个临时变量作为保存return值的动作
0x00c4 00196 (main.go:24) XCHGL AX, AX
0x00c5 00197 (main.go:24) CALL runtime.deferreturn(SB)
0x00ca 00202 (main.go:24) MOVQ 128(SP), BP
0x00d2 00210 (main.go:24) ADDQ $136, SP
0x00d9 00217 (main.go:24) RET
1
2
3
4
5
6
0x009e 00158 (main.go:38)	MOVQ	$1000, "".i+136(SP)
0x00aa 00170 (main.go:39) XCHGL AX, AX
0x00ab 00171 (main.go:39) CALL runtime.deferreturn(SB)
0x00b0 00176 (main.go:39) MOVQ 120(SP), BP
0x00b5 00181 (main.go:39) SUBQ $-128, SP
0x00b9 00185 (main.go:39) RET

代码解读:

为了弄清上述两种情况的区别,我们首先要理解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
2
3
4
5
6
7
8
9
10
// 源码注释在此
// Goexit terminates the goroutine that calls it. No other goroutine is affected.
// Goexit runs all deferred calls before terminating the goroutine. Because Goexit
// is not a panic, any recover calls in those deferred functions will return nil.
//
// Calling Goexit from the main goroutine terminates that goroutine
// without func main returning. Since func main has not returned,
// the program continues execution of other goroutines.
// If all other goroutines exit, the program crashes.

os.Exit(code int)

会结束当前程序,不管你三七二十一。几乎不会用到

1
2
3
4
5
6
// 源码注释在此
// Exit causes the current program to exit with the given status code.
// Conventionally, code zero indicates success, non-zero an error.
// The program terminates immediately; deferred functions are not run.
//
// For portability, the status code should be in the range [0, 125].

panic(v interface{})

停止当前goroutine的正常执行,defer会执行,panic会一直向上传递.

1
2
3
4
5
6
7
8
9
10
11
// 源码注释在此
// The panic built-in function stops normal execution of the current
// goroutine. When a function F calls panic, normal execution of F stops
// immediately. Any functions whose execution was deferred by F are run in
// the usual way, and then F returns to its caller. To the caller G, the
// invocation of F then behaves like a call to panic, terminating G's
// execution and running any deferred functions. This continues until all
// functions in the executing goroutine have stopped, in reverse order. At
// that point, the program is terminated with a non-zero exit code. This
// termination sequence is called panicking and can be controlled by the
// built-in function recover.

结尾

多看源码,多实验,好记性不如烂笔头。