源码剖析panic与recover,看不懂你打我好了!
前言
哈喽,大家好,我是
asong
,今天与大家来聊一聊go
语言中的"throw、try.....catch{}"。如果你之前是一名java
程序员,我相信你一定吐槽过go
语言错误处理方式,但是这篇文章不是来讨论好坏的,我们本文的重点是带着大家看一看panic
与recover
是如何实现的。上一文我们讲解了defer
是如何实现的,但是没有讲解与defer
紧密相连的recover
,想搞懂panic
与recover
的实现也没那么简单,就放到这一篇来讲解了。废话不多说,直接开整。
什么是panic
、recover
Go 语言中 panic
关键字主要用于主动抛出异常,类似 java
等语言中的 throw
关键字。panic
能够改变程序的控制流,调用 panic
后会立刻停止执行当前函数的剩余代码,并在当前 Goroutine 中递归执行调用方的 defer
;
Go 语言中 recover
关键字主要用于捕获异常,让程序回到正常状态,类似 java
等语言中的 try ... catch
。recover
可以中止 panic
造成的程序崩溃。它是一个只能在 defer
中发挥作用的函数,在其他作用域中调用不会发挥作用;
recover
只能在defer
中使用这个在标准库的注释中已经写明白了,我们可以看一下:
// The recover built-in function allows a program to manage behavior of a
// panicking goroutine. Executing a call to recover inside a deferred
// function (but not any function called by it) stops the panicking sequence
// by restoring normal execution and retrieves the error value passed to the
// call of panic. If recover is called outside the deferred function it will
// not stop a panicking sequence. In this case, or when the goroutine is not
// panicking, or if the argument supplied to panic was nil, recover returns
// nil. Thus the return value from recover reports whether the goroutine is
// panicking.
func recover() interface{}
这里有一个要注意的点就是recover
必须要要在defer
函数中使用,否则无法阻止panic
。最好的验证方法是先写两个例子:
func main() {example1()example2()
}func example1() {defer func() {if err := recover(); err !=nil{fmt.Println(string(Stack()))}}()panic("unknown")
}func example2() {defer recover()panic("unknown")
}func Stack() []byte {buf := make([]byte, 1024)for {n := runtime.Stack(buf, false)if n < len(buf) {return buf[:n]}buf = make([]byte, 2*len(buf))}
}
运行我们会发现example2()
方法的panic
是没有被recover
住的,导致整个程序直接crash
了。这里大家肯定会有疑问,为什么直接写recover()
就不能阻止panic
了呢。我们在详解defer实现机制(附上三道面试题,我不信你们都能做对)讲解了defer
实现原理,一个重要的知识点**defer
将语句放入到栈中时,也会将相关的值拷贝同时入栈。**所以defer recover()
这种写法在放入defer
栈中时就已经被执行过了,panic
是发生在之后,所以根本无法阻止住panic
。
特性
上面我们简单的介绍了一下什么是panic
与recover
,下面我一起来看看他们有什么特性,避免我们踩坑。
recover
只有在defer
函数中使用才有效,上面已经举例说明了,这里就不在赘述了。panic
允许在defer
中嵌套多次调用.程序多次调用panic
也不会影响defer
函数的正常执行,所以使用defer
进行收尾工作一般来说都是安全的。写个例子验证一下:
func example3() {defer fmt.Println("this is a example3 for defer use panic")defer func() {defer func() {panic("panic defer 2")}()panic("panic defer 1")}()panic("panic example3")
}
// 运行结果
this is a example3 for defer use panic
panic: panic example3panic: panic defer 1panic: panic defer 2
.......... 省略
通过运行结果可以看出panic
不会影响defer
函数的使用,所以他是安全的。
panic
只会对当前Goroutine
的defer
有效,还记得我们上一文分析的deferproc
函数吗?在newdefer
中分配_defer
结构体对象的时,会把分配到的对象链入当前goroutine
的_defer
链表的表头,也就是把延迟调用函数与调用方所在的Goroutine
进行关联。因此当程序发生panic
时只会调用当前 Goroutine 的延迟调用函数是没有问题的。写个例子验证一下:
func main() {go example4()go example5()time.Sleep(10 * time.Second)
}func example4() {fmt.Println("goroutine example4")defer func() {fmt.Println("test defer")}()panic("unknown")
}func example5() {defer fmt.Println("goroutine example5")time.Sleep(5 * time.Second)
}
// 运行结果
goroutine example4
test defer
panic: unknown
............. 省略部分代码
这里我开了两个协程,一个协程会发生panic
,导致程序崩溃,但是只会执行自己所在Goroutine
的延迟函数,所以正好验证了多个 Goroutine
之间没有太多的关联,一个 Goroutine
在 panic
时也不应该执行其他 Goroutine
的延迟函数。
典型应用
其实我们在实际项目开发中,经常会遇到panic
问题, Go 的 runtime
代码中很多地方都调用了 panic
函数,对于不了解 Go 底层实现的新人来说,这无疑是挖了一堆深坑。我们在实际生产环境中总会出现panic
,但是我们的程序仍能正常运行,这是因为我们的框架已经做了recover
,他已经为我们兜住底,比如gin
,我们看一看他是怎么做的。
先看代码部分吧:
func Default() *Engine {debugPrintWARNINGDefault()engine := New()engine.Use(Logger(), Recovery())return engine
}
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
func Recovery() HandlerFunc {return RecoveryWithWriter(DefaultErrorWriter)
}// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
func RecoveryWithWriter(out io.Writer) HandlerFunc {var logger *log.Loggerif out != nil {logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)}return func(c *Context) {defer func() {if err := recover(); err != nil {// Check for a broken connection, as it is not really a// condition that warrants a panic stack trace....................// 省略}}()c.Next()}
}
我们在使用gin
时,第一步会初始化一个Engine
实例,调用Default
方法会把recovery middleware
附上,recovery
中使用了defer
函数,通过recover
来阻止panic
,当发生panic
时,会返回500错误码。这里有一个需要注意的点是只有主程序中的panic
是会被自动recover
的,协程中出现panic
会导致整个程序crash
。还记得我们上面讲的第三个特性嘛,一个协程会发生panic
,导致程序崩溃,但是只会执行自己所在Goroutine
的延迟函数,所以正好验证了多个 Goroutine
之间没有太多的关联,一个 Goroutine
在 panic
时也不应该执行其他 Goroutine
的延迟函数。 这就能解释通了吧, 所以为了程序健壮性,我们应该自己主动检查我们的协程程序,在我们的协程函数中添加recover
是很有必要的,比如这样:
func main() {r := gin.Default()r.GET("/asong/test/go-panic", func(ctx *gin.Context) {go func() {defer func() {if err := recover();err != nil{fmt.Println(err)}}()panic("panic")}()})r.Run()
}
如果使用的Gin
框架,切记要检查协程中是否会出现panic
,否则线上将付出沉重的代价。非常危险!!!
源码解析
go-version: 1.15.3
我们先来写个简单的代码,看看他的汇编调用:
func main() {defer func() {if err:= recover();err != nil{fmt.Println(err)}}()panic("unknown")
}
执行go tool compile -N -l -S main.go
就可以看到对应的汇编码了,我们截取部分片段分析:
上面重点部分就是画红线的三处,第一步调用runtime.deferprocStack
创建defer
对象,这一步大家可能会有疑惑,我上一文忘记讲个这个了,这里先简单概括一下,defer
总共有三种模型,编译一个函数里只会有一种defer
模式
第一种,堆上分配(deferproc),基本是依赖运行时来分配"_defer"对象并加入延迟参数。在函数的尾部插入
deferreturn
方法来消费defer
link。第二种,栈上分配(deferprocStack),基本上跟堆差不多,只是分配方式改为在栈上分配,压入的函数调用栈存有
_defer
记录,编译器在ssa
过程中会预留defer
空间。第三种,开放编码模式(open coded),不过是有条件的,默认open-coded最多支持8个defer,超过则取消。在构建ssa时如发现gcflags有N禁止优化的参数 或者 return数量 * defer数量超过了 15不适用open-coded模式。并不能处于循环中。
按理说我们的版本是1.15+
,应该使用开放编码模式呀,但是这里怎么还会在栈上分配?注意看呀,伙计们,我在汇编处理时禁止了编译优化,那肯定不会走开放编码模式呀,这个不是重点,我们接着分析上面的汇编。
第二个红线在程序发生panic
时会调用runtime.gopanic
,现在程序处于panic
状态,在函数返回时调用runtime.deferreturn
,也就是调用延迟函数处理。上面这一步是主程序执行部分,下面我们在看一下延迟函数中的执行:
这里最重点的就只有一个,调用runtime.gorecover
,也就是在这一步,对主程序中的panic
进行了恢复了,这就是panic
与recover
的执行过程,接下来我们就仔细分析一下runtime.gopanic
、runtime.gorecover
这两个方法是如何实现的!
_panic结构
在讲defer
实现机制时,我们一起看过defer
的结构,其中有一个字段就是_panic
,是触发defer
的作用,我们来看看的panic
的结构:
type _panic struct {argp unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblinkarg interface{} // argument to paniclink *_panic // link to earlier panicpc uintptr // where to return to in runtime if this panic is bypassedsp unsafe.Pointer // where to return to in runtime if this panic is bypassedrecovered bool // whether this panic is overaborted bool // the panic was abortedgoexit bool
}
简单介绍一下上面的字段:
argp
是指向defer
调用时参数的指针。arg
是我们调用panic
时传入的参数link
指向的是更早调用runtime._panic
结构,也就是说painc
可以被连续调用,他们之间形成链表recovered
表示当前runtime._panic
是否被recover
恢复aborted
表示当前的panic
是否被强行终止
上面的pc
、sp
、goexit
我们单独讲一下,runtime
包中有一个Goexit
方法,Goext
能够终止调用它的goroutine
,其他的goroutine
是不受影响的,goexit
也会在终止goroutine
之前运行所有延迟调用函数,Goexit
不是一个panic
,所以这些延迟函数中的任何recover
调用都将返回nil
。如果我们在主函数中调用了Goexit
会终止该goroutine
但不会返回func main
。由于func main
没有返回,因此程序将继续执行其他gorountine
,直到所有其他goroutine
退出,程序才会crash
。写个简单的例子:
func main() {go func() {defer func() {if err := recover(); err != nil {fmt.Println(err)}}()runtime.Goexit()}()go func() {for true {fmt.Println("test")}}()runtime.Goexit()fmt.Println("main")select {}
}
运行上面的例子你就会发现,即使在主goroutine
中调用了runtime.Goexit
,其他goroutine
是没有任何影响的。所以结构中的pc
、sp
、goexit
三个字段都是为了修复runtime.Goexit
,这三个字段就是为了保证该函数的一定会生效,因为如果在defer
中发生panic
,那么goexit
函数就会被取消,所以才有了这三个字段做保护。看这个例子:
func main() {maybeGoexit()
}
func maybeGoexit() {defer func() {fmt.Println(recover())}()defer panic("cancelled Goexit!")runtime.Goexit()
}
英语好的可以看一看这个:https://github.com/golang/go/issues/29226,这就是上面的一个例子,这里就不过多解释了,了解就好。
下面就开始我们的重点吧~。
gopanic
gopanic的代码有点长,我们一点一点来分析:
第一部分,判断
panic
类型:
gp := getg()if gp.m.curg != gp {print("panic: ")printany(e)print("\n")throw("panic on system stack")}if gp.m.mallocing != 0 {print("panic: ")printany(e)print("\n")throw("panic during malloc")}if gp.m.preemptoff != "" {print("panic: ")printany(e)print("\n")print("preempt off reason: ")print(gp.m.preemptoff)print("\n")throw("panic during preemptoff")}if gp.m.locks != 0 {print("panic: ")printany(e)print("\n")throw("panic holding locks")}
根据不同的类型判断当前发生panic
错误,这里没什么多说的,接着往下看。
第二部分,确保每个
recover
都试图恢复当前协程中最新产生的且尚未恢复的panic
var p _panic // 声明一个panic结构p.arg = e // 把panic传入的值赋给`arg`p.link = gp._panic // 指向runtime.panic结构gp._panic = (*_panic)(noescape(unsafe.Pointer(&p)))atomic.Xadd(&runningPanicDefers, 1)// By calculating getcallerpc/getcallersp here, we avoid scanning the// gopanic frame (stack scanning is slow...)addOneOpenDeferFrame(gp, getcallerpc(), unsafe.Pointer(getcallersp()))for {d := gp._defer // 获取当前gorourine的 deferif d == nil {break // 如果没有defer直接退出了}// If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic),// take defer off list. An earlier panic will not continue running, but we will make sure below that an// earlier Goexit does continue running.if d.started {if d._panic != nil {d._panic.aborted = true}d._panic = nilif !d.openDefer {// For open-coded defers, we need to process the// defer again, in case there are any other defers// to call in the frame (not including the defer// call that caused the panic).d.fn = nilgp._defer = d.linkfreedefer(d)continue}}// Mark defer as started, but keep on list, so that traceback// can find and update the defer's argument frame if stack growth// or a garbage collection happens before reflectcall starts executing d.fn.d.started = true// Record the panic that is running the defer.// If there is a new panic during the deferred call, that panic// will find d in the list and will mark d._panic (this panic) aborted.d._panic = (*_panic)(noescape(unsafe.Pointer(&p)))
上面的代码不太好说的部分,我添加了注释,就不在这解释一遍了,直接看 d.Started
部分,这里的意思是如果defer
是由先前的panic
或Goexit
启动的(循环处理回到这里,这触发了新的panic
),将defer
从列表中删除。早期的panic
将不会继续运行,但我们将确保早期的Goexit会继续运行,代码中的if d._panic != nil{d._panic.aborted =true}
就是确保将先前的panic
终止掉,将aborted
设置为true
,在下面执行recover
时保证goexit
不会被取消。
第三部分,
defer
内联优化调用性能
if !d.openDefer {// For open-coded defers, we need to process the// defer again, in case there are any other defers// to call in the frame (not including the defer// call that caused the panic).d.fn = nilgp._defer = d.linkfreedefer(d)continue}done := trueif d.openDefer {done = runOpenDeferFrame(gp, d)if done && !d._panic.recovered {addOneOpenDeferFrame(gp, 0, nil)}} else {p.argp = unsafe.Pointer(getargp(0))reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))}
上面的代码都是截图片段,这些部分都是为了判断当前defer
是否可以使用开发编码模式,具体怎么操作的就不展开了。
第四部分,
gopanic
中执行程序恢复
在第三部分进行defer
内联优化选择时会执行调用延迟函数(reflectcall就是这个作用),也就是会调用runtime.gorecover
把recoverd = true
,具体这个函数的操作留在下面讲,因为runtime.gorecover
函数并不包含恢复程序的逻辑,程序的恢复是在gopanic
中执行的。先看一下代码:
if p.recovered { // 在runtime.gorecover中设置为truegp._panic = p.link if gp._panic != nil && gp._panic.goexit && gp._panic.aborted { // A normal recover would bypass/abort the Goexit. Instead,// we return to the processing loop of the Goexit.gp.sigcode0 = uintptr(gp._panic.sp)gp.sigcode1 = uintptr(gp._panic.pc)mcall(recovery)throw("bypassed recovery failed") // mcall should not return}atomic.Xadd(&runningPanicDefers, -1)if done {// Remove any remaining non-started, open-coded// defer entries after a recover, since the// corresponding defers will be executed normally// (inline). Any such entry will become stale once// we run the corresponding defers inline and exit// the associated stack frame.d := gp._defervar prev *_deferfor d != nil {if d.openDefer {if d.started {// This defer is started but we// are in the middle of a// defer-panic-recover inside of// it, so don't remove it or any// further defer entriesbreak}if prev == nil {gp._defer = d.link} else {prev.link = d.link}newd := d.linkfreedefer(d)d = newd} else {prev = dd = d.link}}}gp._panic = p.link// Aborted panics are marked but remain on the g.panic list.// Remove them from the list.for gp._panic != nil && gp._panic.aborted {gp._panic = gp._panic.link}if gp._panic == nil { // must be done with signalgp.sig = 0}// Pass information about recovering frame to recovery.gp.sigcode0 = uintptr(sp)gp.sigcode1 = pcmcall(recovery)throw("recovery failed") // mcall should not return}
这段代码有点长,主要就是分为两部分:
第一部分主要是这个判断if gp._panic != nil && gp._panic.goexit && gp._panic.aborted { ... }
,正常recover是会绕过Goexit
的,所以为了解决这个,添加了这个判断,这样就可以保证Goexit
也会被recover
住,这里是通过从runtime._panic
中取出了程序计数器pc
和栈指针sp
并且调用runtime.recovery
函数触发goroutine
的调度,调度之前会准备好 sp
、pc
以及函数的返回值。
第二部分主要是做panic
的recover
,这也与上面的流程基本差不多,他是从runtime._defer
中取出了程序计数器pc
和栈指针sp
并调用recovery
函数触发Goroutine
,跳转到recovery
函数是通过runtime.call
进行的,我们看一下其源码(src/runtime/asm_amd64.s 289行):
// func mcall(fn func(*g))
// Switch to m->g0's stack, call fn(g).
// Fn must never return. It should gogo(&g->sched)
// to keep running g.
TEXT runtime·mcall(SB), NOSPLIT, $0-8MOVQ fn+0(FP), DIget_tls(CX)MOVQ g(CX), AX // save state in g->schedMOVQ 0(SP), BX // caller's PCMOVQ BX, (g_sched+gobuf_pc)(AX)LEAQ fn+0(FP), BX // caller's SPMOVQ BX, (g_sched+gobuf_sp)(AX)MOVQ AX, (g_sched+gobuf_g)(AX)MOVQ BP, (g_sched+gobuf_bp)(AX)// switch to m->g0 & its stack, call fnMOVQ g(CX), BXMOVQ g_m(BX), BXMOVQ m_g0(BX), SICMPQ SI, AX // if g == m->g0 call badmcallJNE 3(PC)MOVQ $runtime·badmcall(SB), AXJMP AXMOVQ SI, g(CX) // g = m->g0MOVQ (g_sched+gobuf_sp)(SI), SP // sp = m->g0->sched.spPUSHQ AXMOVQ DI, DXMOVQ 0(DI), DICALL DIPOPQ AXMOVQ $runtime·badmcall2(SB), AXJMP AXRET
因为go
语言中的runtime
环境是有自己的堆栈和goroutine
,recovery
函数也是在runtime
环境执行的,所以要调度到m->g0
来执行recovery
函数,我们在看一下recovery
函数:
// Unwind the stack after a deferred function calls recover
// after a panic. Then arrange to continue running as though
// the caller of the deferred function returned normally.
func recovery(gp *g) {// Info about defer passed in G struct.sp := gp.sigcode0pc := gp.sigcode1// d's arguments need to be in the stack.if sp != 0 && (sp < gp.stack.lo || gp.stack.hi < sp) {print("recover: ", hex(sp), " not in [", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n")throw("bad recovery")}// Make the deferproc for this d return again,// this time returning 1. The calling function will// jump to the standard return epilogue.gp.sched.sp = spgp.sched.pc = pcgp.sched.lr = 0gp.sched.ret = 1gogo(&gp.sched)
}
在recovery
函数中,利用 g
中的两个状态码回溯栈指针 sp 并恢复程序计数器 pc 到调度器中,并调用 gogo
重新调度 g
,将 g
恢复到调用 recover
函数的位置, goroutine 继续执行,recovery
在调度过程中会将函数的返回值设置为1。这个有什么作用呢?在deferproc
函数中找到了答案:
//go:nosplit
func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn............ 省略
// deferproc returns 0 normally.// a deferred func that stops a panic// makes the deferproc return 1.// the code the compiler generates always// checks the return value and jumps to the// end of the function if deferproc returns != 0.return0()// No code can go here - the C return register has// been set and must not be clobbered.
}
当延迟函数中recover
了一个panic
时,就会返回1,当 runtime.deferproc
函数的返回值是 1 时,编译器生成的代码会直接跳转到调用方函数返回之前并执行 runtime.deferreturn
,跳转到runtime.deferturn
函数之后,程序就已经从panic
恢复了正常的逻辑。
第五部分,如果没有遇到
runtime.gorecover
就会依次遍历所有的runtime._defer
,在最后调用fatalpanic
中止程序,并打印panic
参数返回错误码2。
// fatalpanic implements an unrecoverable panic. It is like fatalthrow, except
// that if msgs != nil, fatalpanic also prints panic messages and decrements
// runningPanicDefers once main is blocked from exiting.
//
//go:nosplit
func fatalpanic(msgs *_panic) {pc := getcallerpc()sp := getcallersp()gp := getg()var docrash bool// Switch to the system stack to avoid any stack growth, which// may make things worse if the runtime is in a bad state.systemstack(func() {if startpanic_m() && msgs != nil {// There were panic messages and startpanic_m// says it's okay to try to print them.// startpanic_m set panicking, which will// block main from exiting, so now OK to// decrement runningPanicDefers.atomic.Xadd(&runningPanicDefers, -1)printpanics(msgs)}docrash = dopanic_m(gp, pc, sp)})if docrash {// By crashing outside the above systemstack call, debuggers// will not be confused when generating a backtrace.// Function crash is marked nosplit to avoid stack growth.crash()}systemstack(func() {exit(2)})*(*int)(nil) = 0 // not reached
}
在这里runtime.fatalpanic
实现了无法被恢复的程序崩溃,它在中止程序之前会通过 runtime.printpanics
打印出全部的 panic
消息以及调用时传入的参数。
好啦,至此整个gopanic
方法就全部看完了,接下来我们再来看一看gorecover
方法。
gorecover
这个函数就简单很多了,代码量比较少,先看一下代码吧:
// The implementation of the predeclared function recover.
// Cannot split the stack because it needs to reliably
// find the stack segment of its caller.
//
// TODO(rsc): Once we commit to CopyStackAlways,
// this doesn't need to be nosplit.
//go:nosplit
func gorecover(argp uintptr) interface{} {// Must be in a function running as part of a deferred call during the panic.// Must be called from the topmost function of the call// (the function used in the defer statement).// p.argp is the argument pointer of that topmost deferred function call.// Compare against argp reported by caller.// If they match, the caller is the one who can recover.gp := getg()p := gp._panicif p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {p.recovered = truereturn p.arg}return nil
}
首先获取当前所在的Goroutine
,如果当前Goroutine
没有调用panic
,那么该函数会直接返回nil
,是否能recover
住该panic
的判断条件必须四个都吻合,p.Goexit
判断当前是否是goexit
触发的,如果是则无法revocer
住,上面讲过会在gopanic
中执行进行recover
。argp
是最顶层延迟函数调用的实参指针,与调用者的argp
进行比较,如果匹配说明调用者是可以recover
,直接将recovered
字段设置为true
就可以了。这里主要的作用就是判断当前panic
是否可以recover
,具体的恢复逻辑还是由gopanic
函数负责的。
流程总结
上面看了一篇源码,肯定也是一脸懵逼吧~。这正常,毕竟文字诉说,只能到这个程度了,还是要自己结合带去去看,这里只是起一个辅助作用,最后做一个流程总结吧。
在程序执行过程中如果遇到
panic
,那么会调用runtime.gopanic
,然后取当前Goroutine
的defer
链表依次执行。在调用
defer
函数是如果有recover
就会调用runtime.gorecover
,在gorecover
中会把runtime._panic
中的recoved
标记为true
,这里只是标记的作用,恢复逻辑仍在runtime.panic
中。在
gopanic
中会执行defer
内联优化、程序恢复逻辑。在程序恢复逻辑中,会进行判断,如果是触发是runtime.Goexit
,也会进行recovery
。panic
也会进行recovery
,主要逻辑是runtime.gopanic
会从runtime._defer
结构体中取出程序计数器pc
和栈指针sp
并调用runtime.recovery
函数恢复程序。runtime.recvoery
函数中会根据传入的pc
和sp
在gogo
中跳转回runtime.deferproc
,如果返回值为1,就会调用runtime.deferreturn
恢复正常流程。在
gopanic
执行完所有的_defer
并且也没有遇到recover
,那么就会执行runtime.fatalpanic
终止程序,并返回错误码2.
这就是这个逻辑流程,累死我了。。。。
小彩蛋
结尾给大家发一个小福利,哈哈,这个福利就是如果避免出现panic
,要注意这些:
数组/切片下标越界,对于
go
这种静态语言来说,下标越界是致命问题。不要访问未初始化的指针或
nil
指针不要往已经
close
的chan
里发送数据map
不是线程安全的,不要并发读写map
这几个是比较典型的,还有很多会发生panic
的地方,交给你们自行学习吧~。
总结
好啦,这篇文章就到这里啦,素质三连(分享、点赞、在看)都是笔者持续创作更多优质内容的动力!
创建了一个Golang学习交流群,欢迎各位大佬们踊跃入群,我们一起学习交流。入群方式:加我vx拉你入群,或者公众号获取入群二维码
结尾给大家发一个小福利吧,最近我在看[微服务架构设计模式]这一本书,讲的很好,自己也收集了一本PDF,有需要的小伙可以到自行下载。获取方式:关注公众号:[Golang梦工厂],后台回复:[微服务],即可获取。
我翻译了一份GIN中文文档,会定期进行维护,有需要的小伙伴后台回复[gin]即可下载。
翻译了一份Machinery中文文档,会定期进行维护,有需要的小伙伴们后台回复[machinery]即可获取。
我是asong,一名普普通通的程序猿,让我们一起慢慢变强吧。欢迎各位的关注,我们下期见~~~
推荐往期文章:
machinery-go异步任务队列
详解defer实现机制
真的理解interface了嘛
Leaf—Segment分布式ID生成系统(Golang实现版本)
十张动图带你搞懂排序算法(附go实现代码)
go参数传递类型
手把手教姐姐写消息队列
常见面试题之缓存雪崩、缓存穿透、缓存击穿
详解Context包,看这一篇就够了!!!
go-ElasticSearch入门看这一篇就够了(一)
面试官:go中for-range使用过吗?这几个问题你能解释一下原因吗
源码剖析panic与recover,看不懂你打我好了!相关推荐
- Vue 3源码剖析,看这篇就够了
大家好,我是若川.源码的重要性相信不用再多说什么了吧,特别是用Vue 框架的,一般在面试的时候面试官多多少少都会考察源码层面的内容,比如: 如何理解虚拟Dom? Vue 3为什么这么快? Vue 3的 ...
- 《STL源码剖析》学习--6章--_rotate算法分析
最近在看侯捷的<STL源码剖析>,其中有许多不太明白之处,后经分析或查找资料有了些理解,现记录一下. <STL源码剖析>学习--6章--random access ite ...
- 源码剖析 Netty 服务启动 NIO
如果这个文章看不懂的话 , 建议反复阅读 Netty 与 Reactor 开篇立意.引用网友好的建议.看源码要针对性的看,最佳实践就是,带着明确的目的去看源码.抓主要问题,放弃小问题.主要的逻辑理解了 ...
- kubernetes源码剖析读后感(二)
注:结合书中的大概内容以及笔者自身的k8s经验 总结学到的一些新知识每一篇篇幅不会很长 书很棒强烈推荐买一本读 本次读书来自于<kubernetes源码剖析> 作者郑东旭 因为第二章确实笔 ...
- SpringDataJPA+Hibernate框架源码剖析(六)@PersistenceContext和@Autowired注入EntityManager的区别
SpringDataJPA+Hibernate框架源码剖析系列文章: SpringDataJPA+Hibernate框架源码剖析(一)框架介绍 SpringDataJPA+Hibernate框架源码剖 ...
- k8s-client-go源码剖析(一)
简介:云原生社区活动---Kubernetes源码剖析第一期 有幸参与云原生社区举办的Kubernetes源码剖析活动,活动主要以书籍<Kubernetes源码剖析>为主要思路进行展开,提 ...
- cron表达式解析 + robfig/cron 源码剖析
robfiig/cron 源码剖析 Cron 表达式 参考wiki https://en.wikipedia.org/wiki/Cron robfiig/cron项目信息 下载地址 https://g ...
- Go内核源码剖析 一 程序执行启动过程
go内核源码剖析 一 这篇是看雨痕大佬的书所做练习的笔记,(其实后面部分基本都是抄的,但是都实践了) 由于电脑抽风,使用的是win10的Linux子系统,功能不完善,很多跟踪支持性不好(可以算是抄的原 ...
- LocalBroadcastManager源码剖析
Handler系列文章: Handler源码剖析 HandlerThread与IntentService源码剖析 LocalBroadcastManager源码剖析 BroadcastReceiver ...
- 0215前端日报:vue源码剖析思维导图
给 「前端开发博客」 加星标,每天打卡学习 长按二维码即可识别"进入网页"查看哟~ 1.vue源码剖析思维导图(一) 趁这个"难得"的假期,学习了一下vue源码 ...
最新文章
- 《精通并发与Netty》学习笔记(13 - 解决TCP粘包拆包(一)概念及实例演示)
- Dos命令控制Mysql语句(自己老记不住)
- 计算机组成原理怎么考察的,计算机组成原理课程考察报告(论文).doc
- api文档 luci_研究LuCI - 技术手札 - OSCHINA - 中文开源技术交流社区
- 任意进制转化 函数 模板(一)
- 工业交换机性能中的“自适应”该如何理解?
- sql 一列中平均应发工资_劳动者的工资标准,应如何认定?
- 7.Java常用开发工具
- 制作 mysql的rpm文件_自制mysql.rpm安装包
- EfficientNetV2:训练速度快了5~10x,更小,更快,精度更高的EfficientNet
- 今天你的静态变量和静态代码块执行了吗?
- 交换机知识--生成树协议
- 【yarn】INFO ipc.Client Retrying connect to server xxx 8032 Already tried 0 time(s)
- SQL Server 触发器 详细讲解
- bzoj 4320: ShangHai2006 Homework
- 测试的目的_盐雾测试的目的是什么
- Kali安装AWVS
- 利用电力声类比与有限元仿真方法分析亥姆霍兹共振器
- APISpace 尾号限行API接口 免费好用
- 用matlab求系统幅度频率响应,matlab频率响应