今天在看某个开源项目源码的时候发现了一个有意思的代码段
1 2 3 4 5 6 7 8 9
| err = watchFile(tunnelsConfPath, servicesConfPath, func(event fsnotify.Event) { manager.notify() })
func (m *Manager) notify() { m.debounce(func() { m.events <- struct{}{} }) }
|
原理分析
这个 debounce 的作用是,对于如下场景,在 1s 内用户更新了 100 次文件内容,希望程序只根据最后一次更新的文件内容刷新配置,以减小程序的压力。
debounce 实现的主要思想是:延迟执行相应的函数。比如说,t0 时需要执行函数,那么就会被延迟到 t0 + 500ms 执行,如果在此期间函数又被执行,比如 t1,则取消之前需要在 t0 + 500ms 执行的定时器,变为 t1 + 500ms 时执行 ,简单的实现如下:
1 2 3 4 5 6 7 8 9
| func NewDebounce(after time.Duration) func(f func()) { var timer *time.Timer return func(f func()) { if timer != nil { timer.Stop() } timer = time.AfterFunc(after, f) } }
|
编写如下函数进行测试。虽然内部循环执行了 1000 次,但是 f 只会被执行一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func main() { d := NewDebounce(time.Millisecond * 100) var data int64 f := func() { atomic.AddInt64(&data, 1) } for i := 0; i < 10; i++ { for j := 0; j < 1000; j++ { d(f) } time.Sleep(time.Millisecond * 100) } fmt.Println(data) }
|
开源实现
对于并发环境,debounce 中需要为 timer 加锁,GitHub - bep/debounce: A debouncer written in Go 给出了一个 debounce 实现,代码实现逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| func New(after time.Duration) func(f func()) { d := &debouncer{after: after} return func(f func()) { d.add(f) } }
type debouncer struct { mu sync.Mutex after time.Duration timer *time.Timer }
func (d *debouncer) add(f func()) { d.mu.Lock() defer d.mu.Unlock()
if d.timer != nil { d.timer.Stop() } d.timer = time.AfterFunc(d.after, f) }
|