美文网首页
go语言的 singleflight的源码分析和应用案例

go语言的 singleflight的源码分析和应用案例

作者: 鸿雁长飞光不度 | 来源:发表于2023-03-27 23:11 被阅读0次

Go语言的singleflight是一个常用的并发控制工具,它能够避免重复执行相同的操作,并发地执行只需一次的操作。在本文中,我们将对singleflight的源代码进行分析,并介绍一些使用案例。

1. 什么是Singleflight?

Go语言的singleflight是一个常用的并发控制工具,它能够避免重复执行相同的操作,并发地执行只需一次的操作。在本文中,我们将对singleflight的源代码进行分析,并介绍一些使用案例。

什么是Singleflight?
Singleflight是一个Go语言的库,用于避免在高并发环境中执行相同的操作。当多个goroutine需要同时执行同一个操作时,singleflight会保证只有一个goroutine执行该操作,并将结果返回给其他goroutine,从而避免了重复执行相同操作的问题。

单个操作通常是一个昂贵的操作,比如访问数据库或调用远程API,而Singleflight能够帮助我们避免重复执行这样的操作,提高程序的性能和稳定性。

2. Singleflight的源代码分析

Singleflight库的核心代码只有几十行,主要包括了一个Group结构体和一个Do方法,下面是Group结构体的定义:

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

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

type Result struct {
    val interface{}
    err error
    shared bool
}

Group结构体中包含了两个map,一个是m,存储了每个操作的call结构体,另一个是dup,存储了正在执行的操作的channel。当多个goroutine同时执行同一个操作时,只有第一个goroutine会执行操作,其他goroutine会等待第一个goroutine执行完后,直接从m中获取结果。

Do方法是Singleflight库的核心方法,它的定义如下:

func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
    g.mu.Lock()
    if g.m == nil {
        g.m = make(map[string]*call)
    }
    if c, ok := g.m[key]; ok {
        g.mu.Unlock()
        c.wg.Wait()
        return c.val, c.err
    }
    if ch, ok := g.dup[key]; ok {
        g.mu.Unlock()
        res := <-ch
        if res.shared {
            return res.val, res.err
        }
        return fn()
    }
    ch := make(chan Result, 1)
    g.dup[key] = ch
    g.mu.Unlock()

    c := new(call)
    c.wg.Add(1)
    c.val, c.err = fn()
    c.wg.Done()

    g.mu.Lock()
    delete(g.dup, key)
    if _, ok := g.m[key]; !ok {
        g.m[key] = c
    }
    g.mu.Unlock()

    ch <- Result{c.val, c.err, false}

    return c.val, c.err
}

Do方法的流程如下:

  1. 先获取锁,然后检查操作是否正在执行中(通过m和dup两个map),如果是,则等待操作执行完成并返回结果。
  2. 如果操作没有在执行中,则在dup中创建一个channel,并将其存储在dup中,然后释放锁。这样其他正在等待执行该操作的goroutine就能从dup中获取该channel,防止重复执行操作。
  3. 执行操作,并将结果存储在call结构体中。
  4. 获取锁,并删除dup中的channel。然后再次检查操作是否已经在执行中,如果没有,则将结果存储在m中。
  5. 释放锁,并将结果发送给其他等待执行该操作的goroutine。

3. 引用案例

  • 避免重复调用接口:当多个goroutine需要同时调用同一个接口时,Singleflight能够保证只有一个goroutine执行该操作,从而避免了重复调用接口的问题。
  • 避免重复访问数据库:当多个goroutine需要同时访问同一个数据库时,Singleflight能够保证只有一个goroutine执行该操作,从而避免了重复访问数据库的问题。
  • 避免重复计算:当多个goroutine需要同时计算同一个值时,Singleflight能够保证只有一个goroutine执行该操作,从而避免了重复计算的问题。
package main

import (
    "fmt"
    "sync"
    "time"

    "golang.org/x/sync/singleflight"
)

func main() {
    var group singleflight.Group
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            result, err, _ := group.Do("calculation", func() (interface{}, error) {
                fmt.Println("Start calculating...")
                time.Sleep(time.Second) // 模拟计算耗时
                fmt.Println("Calculation finished.")
                return 42, nil
            })
            if err != nil {
                fmt.Printf("error: %v\n", err)
                return
            }
            fmt.Printf("result: %v\n", result)
        }()
    }

    wg.Wait()
}

在这个示例中,我们使用Singleflight来避免重复计算一个值。使用group.Do方法计算值,第一次调用时,会进行计算,并将结果存储在group中。其他请求再次调用group.Do方法时,会从group中取出结果,避免了重复计算。

运行上面的程序,可以看到只有第一个goroutine进行了计算,其他goroutine直接返回了结果,避免了重复计算。

Start calculating...
Calculation finished.
result: 42
result: 42
result: 42
result: 42
result: 42

相关文章

网友评论

      本文标题:go语言的 singleflight的源码分析和应用案例

      本文链接:https://www.haomeiwen.com/subject/rbhtddtx.html