defer
Go语言中在函数或方法中可以使用defer关键字搭配闭包等让某些行为延迟执行(在函数或方法返回之前执行),但沿途的defer一定会被执行到。这一点很像java中的finally块,其中的代码一定会被执行到。
try{
...
}catch(Exception e){
...
}finally{
...
}
Go defer
func Defer() (result int) {
defer func() {
fmt.Println("defer1")
}()
defer func() {
fmt.Println("defer2")
}()
defer func() {
fmt.Println("defer3")
}()
defer func() {
result = 100
fmt.Println("defer4")
}()
return 10
}
defer 的执行顺序与编码的顺序是相反的,即后进先出的顺序,也就是栈顺序。
以上代码的输出为:
defer4
defer3
defer2
defer1
若此时打印Defer()的返回值,你会发现是100。我们明明在Defer()的最后用return语句返回了10这个数字。那么为什么会产生100这个数据呢。经过观察,你会发现,在第四个defer中我们手动将result返回值修改为了100。
Go语言中,包含可执行到的defer语句的函数返回顺序是:先给函数返回值赋值,再执行defer语句,而后返回。在执行defer语句的时候,可以对返回值进行操作。
panic
// 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.
func panic(v any)
Go 语言使用panic来让程序显式的崩溃。
func Panic() {
f1()
}
func f1() {
fmt.Println("f1")
f2()
}
func f2() {
fmt.Println("f2")
f3()
}
func f3() {
fmt.Println("f3")
panic("f3 panic")
}
在程序崩溃时,会打印堆栈信息,其中包括函数的调用链。
f1
f2
f3
panic: f3 panic
goroutine 1 [running]:
main.f3(...)
.../main.go:44
main.f2()
.../main.go:40 +0xa8
main.f1()
.../main.go:36 +0x57
main.Panic(...)
.../main.go:32
main.main()
.../main.go:57 +0x18
recover
// 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() any
recover 可以阻断程序的崩溃,让程序继续向下执行,但recover后,程序的上下文很可能已经被破坏,所以要权衡是否让程序继续执行。其使用时须与defer配合使用才会生效。
func f1() {
fmt.Println("f1")
f2()
}
func f2() {
fmt.Println("f2")
f3()
}
func f3() {
fmt.Println("f3")
panic("f3 panic")
}
func Recover() {
defer func() {
if res := recover(); res != nil {
fmt.Println("拦截panic", res)
}
}()
f1()
}
以下是recover拦截panic的结果:
f1
f2
f3
拦截panic f3 panic
recover可以恢复处于同一上下文的panic,用go关键字创建的异步语句是无法recover的。
func f1() {
fmt.Println("f1")
f2()
}
func f2() {
fmt.Println("f2")
f3()
}
func f3() {
fmt.Println("f3")
panic("f3 panic")
}
func Recover() {
defer func() {
if res := recover(); res != nil {
fmt.Println("拦截panic", res)
}
}()
go f1() // 无法拦截
}
func main() {
Recover()
time.Sleep(time.Second)
}
出现多级panic时,只需在调用链最顶端recover即可
func f1() {
fmt.Println("f1")
defer f2()
panic("f1 panic")
}
func f2() {
//defer func() {
// if res := recover(); res != nil {
// fmt.Println("拦截panic", res)
// }
//}()
fmt.Println("f2")
defer f3()
panic("f2 panic")
}
func f3() {
fmt.Println("f3")
panic("f3 panic")
}
func Recover() {
defer func() {
if res := recover(); res != nil {
fmt.Println("拦截panic", res)
}
}()
f1()
}
以下行为是非必要的操作
func f1() {
defer func() {
if res := recover(); res != nil {
fmt.Println("拦截panic", res)
}
}()
fmt.Println("f1")
f2()
panic("f1 panic")
}
func f2() {
defer func() {
if res := recover(); res != nil {
fmt.Println("拦截panic", res)
}
}()
fmt.Println("f2")
f3()
panic("f2 panic")
}
func f3() {
defer func() {
if res := recover(); res != nil {
fmt.Println("拦截panic", res)
}
}()
fmt.Println("f3")
panic("f3 panic")
}
可以对recover做简单的封装—> Recover
func f1() {
defer Recover()
fmt.Println("f1")
f2()
panic("f1 panic")
}
func f2() {
fmt.Println("f2")
f3()
panic("f2 panic")
}
func f3() {
fmt.Println("f3")
panic("f3 panic")
}
func Recover() {
if res := recover(); res != nil {
fmt.Println("拦截panic", res)
}
}
recover可以拦截部分panic,但是runtime级别的panic,比如并发读写map,out of memory等用户代码中的recover是无法拦截的,只能任由程序崩溃。
defer经过多次演进,其后的函数性能与普通函数的性能已经接近,其底层最初使用链表形式存储,复制及堆栈移动的开销十分大,后来Go编译时将defer语句内联进代码,效率得以提升,同时用位图维护可能执行到的defer。
defer,panic,recover在Go运行时g的结构体中可以看见其身影。
src/runtime/runtime2.go
// A _defer holds an entry on the list of deferred calls.
// If you add a field here, add code to clear it in deferProcStack.
// This struct must match the code in cmd/compile/internal/ssagen/ssa.go:deferstruct
// and cmd/compile/internal/ssagen/ssa.go:(*state).call.
// Some defers will be allocated on the stack and some on the heap.
// All defers are logically part of the stack, so write barriers to
// initialize them are not required. All defers must be manually scanned,
// and for heap defers, marked.
type _defer struct {
started bool
heap bool
// openDefer indicates that this _defer is for a frame with open-coded
// defers. We have only one defer record for the entire frame (which may
// currently have 0, 1, or more defers active).
openDefer bool
sp uintptr // sp at time of defer
pc uintptr // pc at time of defer
fn func() // can be nil for open-coded defers
_panic *_panic // panic that is running defer
link *_defer // next defer on G; can point to either heap or stack!
// If openDefer is true, the fields below record values about the stack
// frame and associated function that has the open-coded defer(s). sp
// above will be the sp for the frame, and pc will be address of the
// deferreturn call in the function.
fd unsafe.Pointer // funcdata for the function associated with the frame
varp uintptr // value of varp for the stack frame
// framepc is the current pc associated with the stack frame. Together,
// with sp above (which is the sp associated with the stack frame),
// framepc/sp can be used as pc/sp pair to continue a stack trace via
// gentraceback().
framepc uintptr
}
// A _panic holds information about an active panic.
//
// A _panic value must only ever live on the stack.
//
// The argp and link fields are stack pointers, but don't need special
// handling during stack growth: because they are pointer-typed and
// _panic values only live on the stack, regular stack pointer
// adjustment takes care of them.
type _panic struct {
argp unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
arg any // argument to panic
link *_panic // link to earlier panic
pc uintptr // where to return to in runtime if this panic is bypassed
sp unsafe.Pointer // where to return to in runtime if this panic is bypassed
recovered bool // whether this panic is over
aborted bool // the panic was aborted
goexit bool
}
type g struct {
// Stack parameters.
// stack describes the actual stack memory: [stack.lo, stack.hi).
// stackguard0 is the stack pointer compared in the Go stack growth prologue.
// It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.
// stackguard1 is the stack pointer compared in the C stack growth prologue.
// It is stack.lo+StackGuard on g0 and gsignal stacks.
// It is ~0 on other goroutine stacks, to trigger a call to morestackc (and crash).
stack stack // offset known to runtime/cgo
stackguard0 uintptr // offset known to liblink
stackguard1 uintptr // offset known to liblink
_panic *_panic // innermost panic - offset known to liblink
_defer *_defer // innermost defer
m *m // current m; offset known to arm liblink
sched gobuf
syscallsp uintptr // if status==Gsyscall, syscallsp = sched.sp to use during gc
syscallpc uintptr // if status==Gsyscall, syscallpc = sched.pc to use during gc
stktopsp uintptr // expected sp at top of stack, to check in traceback
// param is a generic pointer parameter field used to pass
// values in particular contexts where other storage for the
// parameter would be difficult to find. It is currently used
// in three ways:
// 1. When a channel operation wakes up a blocked goroutine, it sets param to
// point to the sudog of the completed blocking operation.
// 2. By gcAssistAlloc1 to signal back to its caller that the goroutine completed
// the GC cycle. It is unsafe to do so in any other way, because the goroutine's
// stack may have moved in the meantime.
// 3. By debugCallWrap to pass parameters to a new goroutine because allocating a
// closure in the runtime is forbidden.
param unsafe.Pointer
atomicstatus uint32
stackLock uint32 // sigprof/scang lock; TODO: fold in to atomicstatus
goid int64
schedlink guintptr
waitsince int64 // approx time when the g become blocked
waitreason waitReason // if status==Gwaiting
preempt bool // preemption signal, duplicates stackguard0 = stackpreempt
preemptStop bool // transition to _Gpreempted on preemption; otherwise, just deschedule
preemptShrink bool // shrink stack at synchronous safe point
// asyncSafePoint is set if g is stopped at an asynchronous
// safe point. This means there are frames on the stack
// without precise pointer information.
asyncSafePoint bool
paniconfault bool // panic (instead of crash) on unexpected fault address
gcscandone bool // g has scanned stack; protected by _Gscan bit in status
throwsplit bool // must not split stack
// activeStackChans indicates that there are unlocked channels
// pointing into this goroutine's stack. If true, stack
// copying needs to acquire channel locks to protect these
// areas of the stack.
activeStackChans bool
// parkingOnChan indicates that the goroutine is about to
// park on a chansend or chanrecv. Used to signal an unsafe point
// for stack shrinking. It's a boolean value, but is updated atomically.
parkingOnChan uint8
raceignore int8 // ignore race detection events
sysblocktraced bool // StartTrace has emitted EvGoInSyscall about this goroutine
tracking bool // whether we're tracking this G for sched latency statistics
trackingSeq uint8 // used to decide whether to track this G
runnableStamp int64 // timestamp of when the G last became runnable, only used when tracking
runnableTime int64 // the amount of time spent runnable, cleared when running, only used when tracking
sysexitticks int64 // cputicks when syscall has returned (for tracing)
traceseq uint64 // trace event sequencer
tracelastp puintptr // last P emitted an event for this goroutine
lockedm muintptr
sig uint32
writebuf []byte
sigcode0 uintptr
sigcode1 uintptr
sigpc uintptr
gopc uintptr // pc of go statement that created this goroutine
ancestors *[]ancestorInfo // ancestor information goroutine(s) that created this goroutine (only used if debug.tracebackancestors)
startpc uintptr // pc of goroutine function
racectx uintptr
waiting *sudog // sudog structures this g is waiting on (that have a valid elem ptr); in lock order
cgoCtxt []uintptr // cgo traceback context
labels unsafe.Pointer // profiler labels
timer *timer // cached timer for time.Sleep
selectDone uint32 // are we participating in a select and did someone win the race?
// goroutineProfiled indicates the status of this goroutine's stack for the
// current in-progress goroutine profile
goroutineProfiled goroutineProfileStateHolder
// Per-G GC state
// gcAssistBytes is this G's GC assist credit in terms of
// bytes allocated. If this is positive, then the G has credit
// to allocate gcAssistBytes bytes without assisting. If this
// is negative, then the G must correct this by performing
// scan work. We track this in bytes to make it fast to update
// and check for debt in the malloc hot path. The assist ratio
// determines how this corresponds to scan work debt.
gcAssistBytes int64
}
关于defer,panic,recover的源码可以查看src/runtime/panic.go;panic32.go文件
在读Go的源码的时候可能会有一种凌乱的感觉,因为Go语言的特性是其编译时与运行时共同努力的产物,所以有时,你既需要去读编译器的源码,还需要去读运行时的源码,反复跳跃。