https://blog.csdn.net/zhanggqianglovec/article/details/127906942
https://www.cnblogs.com/ricklz/p/14692264.html
https://zhuanlan.zhihu.com/p/624326431
学一点,整一点,基本都是综合别人的,弄成我能理解的内容
panic、recover
1、panic 能够改变程序的控制流,调用 panic 后会立刻停止执行当前函数的剩余代码,并在当前 Goroutine 中递归执行调用方的 defer(在panic()函数前面的已经执行过的defer语句);
2、recover 可以中止 panic 造成的程序崩溃。它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用;
1、panic 只会触发当前goroutine的defer
2、revoce 只有在defer中调用才能生效
3、panic 允许在defer中嵌套多次调用
4、recover没有传入参数,但是有返回值,返回值就是panic传递的值
使用场景
1、程序遇到无法执行下去的错误时,抛出错误,主动结束运行。
2、在调试程序时,通过 panic 来打印堆栈,方便定位错误。
error:可预见的错误
panic:不可预见的异常
需要注意的是,应该尽可能地使用error,而不是使用panic和recover。只有当程序不能继续运行的时候,才应该使用panic和recover机制。
panic有两个合理的用例。
1、发生了一个不能恢复的错误,此时程序不能继续运行。 一个例子就是 web 服务器无法绑定所要求的端口。在这种情况下,就应该使用 panic,因为如果不能绑定端口,啥也做不了。
2、发生了一个编程上的错误。 假如我们有一个接收指针参数的方法,而其他人使用 nil 作为参数调用了它。在这种情况下,我们可以使用panic,因为这是一个编程错误:用 nil 参数调用了一个只能接收合法指针的方法。
在一般情况下,我们不应通过调用panic函数来报告普通的错误,而应该只把它作为报告致命错误的一种方式。当某些不应该发生的场景发生时,我们就应该调用panic。
总结下panic的使用场景:
1、空指针引用
2、下标越界
3、除数为0
4、不应该出现的分支,比如default
5、输入不应该引起函数错误
示例
func main() {// 主线程中的defer函数并不会执行,因为子协程 panic后,主线程中的defer并不会执行defer println("in main")go func() {defer println("in goroutine")fmt.Println("子协程running")panic("子协程崩溃")}()time.Sleep(1 * time.Second)
}//输出
子协程running
in goroutine
panic: 子协程崩溃
func main() {defer fmt.Println("in main")defer func() {if err := recover(); err != nil {fmt.Println("occur error")fmt.Println(err)}}()panic("unknown err")}//输出
occur error
unknown err
in main
嵌套使用panic
func main() {defer fmt.Println("in main")defer func() {defer func() {panic("panic again and again")}()panic("panic again")}()panic("panic once")
}//输出
in main
panic: panic oncepanic: panic againpanic: panic again and again
panic源码
_panic定义
// _panic 保存了一个活跃的 panic
//
// 这个标记了 go:notinheap 因为 _panic 的值必须位于栈上
//
// argp 和 link 字段为栈指针,但在栈增长时不需要特殊处理:因为他们是指针类型且
// _panic 值只位于栈上,正常的栈指针调整会处理他们。
//
//go:notinheap
type _panic struct {argp unsafe.Pointer // panic 期间 defer 调用参数的指针; 无法移动 - liblink 已知arg interface{} // panic的参数link *_panic // link 链接到更早的 panicrecovered bool // panic是否结束aborted bool // panic是否被忽略
}
https://www.cnblogs.com/ricklz/p/14692264.html
编译器指令
https://zhuanlan.zhihu.com/p/624326431
编译器指令是由 Go 编译器处理的,不会影响到代码运行时的行为。在 Go 中,编译器指令都是以 //go: 开头的注释形式。
//go:notinheap 是一个编译器指令,它告诉编译器一个类型不能被分配在堆上。它常常用于运行时包(runtime package)。用户代码通常不应使用这个指令。
//go:embed:这个在 Go 1.16 版本引入,它让你可以在编译时将文件或目录嵌入二进制文件中。
//go:generate:它是一个生成器的指令,你可以在注释后提供一个命令供 go generate 命令执行生成代码。
//go:noinline:这个指令阻止编译器内联一个函数。
//go:linkname:这个指令允许将一个局部函数链接到另一个函数。
//go:directives:你可以查看包含指令列表的文档。
//go:build
是在Go 1.17中引入的新编译器指令,用于替代旧的// +build
条件编译标签。此//go:build nosyn
表示向编译器指示仅在存在nosyn标签时才构建特定的文件。
可以使用如下命令进行编译go build -tags=nosyn
//go:noescape
禁止变量逃逸
变量逃逸是指 编译器将自动地将超出自身生命周期的变量,从函数栈转移到堆中。 golang就是自动将函数外部引用的变量转义到堆栈中,从而保障其他地方使用不会为空或者异常。
如果变量不逃逸,GC压力就会很小,提升性能;
//go:norace
跳过竞态检测
由于Goroutine很方便写并行逻辑,但是往往我们再代码中需要通过go run -race xxx.go测试是否有竞态,会导致编译慢,如果加上norace就可以跳过;