errgroup 的基本使用

2022-10-28 10:39:47 浏览数 (1)

我们在使用 go 编写代码的时候,在错误处理的时候,经常会写出很多 if err != nil ,其实有些时候我们可以使用一些技巧去避免,本文就来讨论两种常见的避免技巧,内部包装错误和 errgroup。

基本 case 实现

代码语言:javascript复制
package main  
  
import "fmt"  
  
func StartUserService() error {  
   fmt.Println("start user service")  
   return nil  
}  
  
func StartGoodsService() error {  
   fmt.Println("start goods service")  
   return nil  
}  
  
func StartOrderService() error {  
   fmt.Println("start order service")  
   return nil  
}  
  
func main() {  
   err := StartUserService()  
   if err != nil {  
      panic(err)  
   }  
   err = StartGoodsService()  
   if err != nil {  
      panic(err)  
   }  
   err = StartOrderService()  
   if err != nil {  
      panic(err)  
   }  
}

这是一个我们常常见到的情况,就是对于多个不同的方法进行调用,比如启动不同的服务,然后每次启动都会返回一个错误,我们都需要对错误进行处理,那么我们如何去优化这个代码呢?

为了简化问题,这个 case 里面我们讨论的基础是,这些启动服务之间没有关联关系,并且只要有其中一个启动失败就直接退出。

内部操作包装实现

代码语言:javascript复制
package main  
  
import "fmt"  
  
type ServiceManager struct {  
   err error  
}  
  
type StartFn func() (err error)  
  
func (s *ServiceManager) Start(sf StartFn) {  
   if s.err != nil {  
      return  
   }  
   s.err = sf()  
}  
  
func (s *ServiceManager) Err() error {  
   return s.err  
}  
  
func StartUserService() error {  
   fmt.Println("start user service")  
   return nil  
}  
  
func StartGoodsService() error {  
   fmt.Println("start goods service")  
   return nil  
}  
  
func StartOrderService() error {  
   fmt.Println("start order service")  
   return nil  
}  
  
func main() {  
   sm := &ServiceManager{}  
  
   sm.Start(StartUserService)  
   sm.Start(StartGoodsService)  
   sm.Start(StartOrderService)  
  
   if err := sm.Err(); err != nil {  
      panic(err)  
   }  
}

当我们遇到重复代码想要合并的时候,第一个想法应该就是抽象,将不同的样子的方法进行抽象,抽象成一个接口。这样抽象之后我们往往就可以通过一次代码来实现相同的功能。

上述的代码中,将启动抽象,并且将错误包装到了一个结构的内部,这也是我们常用的一个技巧,这样的好处在于,在主函数中就没有额外的处理逻辑,只需要无脑的进行调用就可以了。

errgroup 实现

代码语言:javascript复制
package main  
  
import (  
   "context"  
   "fmt"  
   "golang.org/x/sync/errgroup"
)
  
func StartUserService() error {  
   fmt.Println("start user service")  
   return nil  
}  
  
func StartGoodsService() error {  
   fmt.Println("start goods service")  
   return nil  
}  
  
func StartOrderService() error {  
   fmt.Println("start order service")  
   return nil  
}  
  
func main() {  
   gp, _ := errgroup.WithContext(context.Background())  
   gp.Go(StartUserService)  
   gp.Go(StartGoodsService)  
   gp.Go(StartOrderService)  
   if err := gp.Wait(); err != nil {  
      panic(err)  
   }  
}

另一种更为通用的方式是用 errgroup,其实它的原理也是类似的,只不过使用 goroutine 去运行了各个子任务,然后等待子任务全部完成,内部就是通过 waitgroup 实现的。并且当有任意一个出现错误时就会记录错误,最终在 wait 返回。

errgroup 源码见:https://cs.opensource.google/go/x/sync/ /master:errgroup/errgroup.go

扩展

errgroup 还提供了 SetLimitTryGo 方法,通过设定一个并发的上限来确保并发的任务数不会超过限制条件。

总结

  • 本文主要记录了 errgroup 的基本使用,使用明显能比自己亲自使用 waitgroup 要来的方便。
  • 避免重复代码的技巧往往就是,抽象后合并实现,同时使用合理的设计模式

0 人点赞