golang善用go func和defer

一、error与panic:

  • error:可预见的错误
  • panic:不可预见的错误,panic一般通过defer中的recover()捕获
    对于有风险的代码,若发生panic则会导致程序异常退出,例如数组越界。

1. panic的操守

  • panic是有秩序的,退出之前会执行完先处理完当前goroutine已经defer【挂上去】的任务,若某个defer在panic之后,则不会被执行。
  • panic仅保证当前goroutine下的defer都会被调到,但不保证其他协程的defer也会调到
  • 同一goroutine下的已经挂上去的多个defer,回溯执行,即先进后出

例如:

func main() {
	defer fmt.Println("defer main") 
	var user = os.Getenv("USER_")
	go func() {
		defer fmt.Println("defer caller")
		func() {
			defer func() {
				fmt.Println("defer here")
			}()

			if user == "" {
				panic("should set user env.")
			}
		}()
	}()

	time.Sleep(1 * time.Second)
	fmt.Printf("get result")
}

//result:
defer here
defer caller
panic: should set user env.
func main() {
	defer fmt.Println("defer main") 
	var user = os.Getenv("USER_")
	go func() {
		defer fmt.Println("defer caller")
		func() {

			if user == "" {
				panic("should set user env.")
			}

			defer func() {//panic发生时,尚未挂上去,所以不执行
				fmt.Println("defer here")
			}()
		}()
	}()

	time.Sleep(1 * time.Second)
	fmt.Printf("get result")
}

//result:
defer caller
panic: should set user env.

2. panic的处理:defer+recover

  • defer中通过recover截取panic,达到类似try_catch的效果。
  • defer执行在return前或者panic后

	defer func() {
		err := recover()
		if err != nil {//recover若捕获错误,则为panic了
			var errMsg, logMsg string
			switch err.(type) {
			case *global.FlyWebError: //web error
				webErr := err.(*global.FlyWebError)
				code = webErr.Code
				errMsg = util.GetErrMsgByCode(code)
				logMsg = webErr.Msg
			default: //未知错误
				code = util.ERRCODE_UNKNOWN
				errMsg = util.GetErrMsgByCode(code)
				logMsg = fmt.Sprint(err)
			}
			log.Error(common.LOG_CMD, logMsg, 0, 0, 0)
		}
	}()

二、go func()与panic

go func()为开启一个子协程执行该匿名函数内的逻辑。

  • 但是主进程中的defer无法捕获子协程中的panic异常,所以go func()中一般有defer()处理该子协程内发生的panic.

go func()+defer一般用在:多次远程调用,每一次新开一个协程去执行,无需等待结果即可循环下一次,例如:

    wg := sync.WaitGroup{}
    wg.Add(len(arrCommentIds))
	for i := 0; i < len(arrCommentIds); i++ {
		go func(commentId []string) {
			defer func() {
				wg.Done()
				if err0 := recover(); err0 != nil {
					err = errors.New(fmt.Sprint(err0))
				}
			}()
			var tempCommentList *centerpb.CommentList
			tempCommentList, err = rpc.GetCommentInfo(commentId, targetId, filter)
			if err != nil {
				return
			}
			mutex.Lock()
			commentList.Comments = append(commentList.Comments, tempCommentList.Comments...)
			mutex.Unlock()
		}(arrCommentIds[i])
	}
	wg.Wait()//用于等待协程执行完毕

所以defer的用途一般是:

  1. 捕获异常
  2. 封装一些必须要执行的逻辑,顺序执行情况下担心前面代码return了而忘记/走不到该行逻辑。比如锁的释放。

使用defer是有代价的,会造成cpu的浪费,要合理使用。

你可能感兴趣的:(Go开发)