defer,panic,recover

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语言的特性是其编译时与运行时共同努力的产物,所以有时,你既需要去读编译器的源码,还需要去读运行时的源码,反复跳跃。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

metabit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值