HomeBlogProjects

理解 Go singleflight 中的异常处理

sorcererxw

golang.org/x/sync/singleflight 可以避免多个 Goroutine 同时重复地做同一件事情。

它的源码非常简单,整体流程如下:

type call struct {
	wg sync.WaitGroup
	val interface{}
	err error
}

type Group struct {
	mu sync.Mutex       // protects m
	m  map[string]*call // lazily initialized
}

func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error) {
	g.mu.Lock()

	if c, ok := g.m[key]; ok {
		g.mu.Unlock()
		c.wg.Wait()
		...
		return c.val, c.err, true
	}
	c := new(call)
	c.wg.Add(1)
	g.m[key] = c
	g.mu.Unlock()

	g.doCall(c, key, fn)
	return c.val, c.err
}

每一个请求进来的时候:

相比起大的流程,singleflight 的 doCall 函数更有意思:

func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
	normalReturn := false // 是否成功运行完成
	recovered := false // 是否发生 panic

	defer func() {
		if !normalReturn && !recovered {
			c.err = errGoexit // 既没有成功运行,也没有 panic,那么就只有可能是 runtime.Goexit
		}
		g.mu.Lock()
		defer g.mu.Unlock()

		if e, ok := c.err.(*panicError); ok {
			if len(c.chans) > 0 {
				// 为什么要单开一个进程 panic
				go panic(e)
				select {}
			} else {
				panic(e)
			}
		} else if c.err == errGoexit {
		} else {
			for _, ch := range c.chans {
				ch <- Result{c.val, c.err, c.dups > 0}
			}
		}
	}()

	func() { // 在闭包内直接使用 defer,使代码更加简洁
		defer func() {
			if !normalReturn {
				if r := recover(); r != nil {
					c.err = newPanicError(r)
				}
			}
		}()

		c.val, c.err = fn()
		normalReturn = true
	}()

	if !normalReturn {
		recovered = true
	}
}