返回

探秘Go并发利器:Singleflight深入剖析

后端

Singleflight简介

Singleflight是一个轻量级的并发控制库,用于处理共享数据访问的场景。它可以确保在多个并发请求同时访问共享数据时,只有一个请求会真正执行数据获取操作,其他请求都会等待该请求的返回结果。

Singleflight通过使用一个全局的map来存储共享数据。当一个并发请求首次访问共享数据时,Singleflight会检查map中是否已经存在该数据的副本。如果存在,则直接返回该副本;如果不存在,则创建一个新的goroutine来获取数据,并将该goroutine的结果存储在map中。后续的并发请求在访问共享数据时,都会直接从map中获取数据,而无需重新获取。

Singleflight的使用方法

Singleflight的使用非常简单,只需要导入singleflight包,并创建一个Singleflight实例即可。

import "github.com/golang/singleflight"

var sf = singleflight.Group{}

Singleflight实例创建后,就可以使用Do方法来获取共享数据。Do方法接收两个参数:key和f。key是共享数据的唯一标识,f是获取共享数据的函数。

data, err := sf.Do("key", func() (interface{}, error) {
    // 从数据库中获取数据
    return nil, nil
})

Do方法会先检查map中是否已经存在key对应的共享数据。如果存在,则直接返回该数据;如果不存在,则创建一个新的goroutine来获取数据,并将该goroutine的结果存储在map中。后续的并发请求在访问共享数据时,都会直接从map中获取数据,而无需重新获取。

Singleflight的源码解读

Singleflight的源码非常简洁,只有不到100行代码。下面我们将从源码层面来了解Singleflight是如何工作的。

Singleflight的核心数据结构是一个全局的map,用于存储共享数据。map的key是共享数据的唯一标识,map的value是一个*call结构体。

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

call结构体包含了一个sync.WaitGroup对象、一个interface{}类型的val字段和一个error类型的err字段。sync.WaitGroup对象用于等待goroutine完成数据获取操作,val字段存储获取到的数据,err字段存储获取数据时发生的错误。

Singleflight的Do方法首先会检查map中是否已经存在key对应的call结构体。如果存在,则直接返回该call结构体的val字段和err字段。如果不存在,则创建一个新的goroutine来获取数据,并将该goroutine的结果存储在map中。

func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
    // 检查map中是否已经存在key对应的call结构体
    c, ok := g.mu.Load(key)
    if !ok {
        // 创建一个新的call结构体
        c = &call{
            wg: sync.WaitGroup{},
        }

        // 将call结构体存储在map中
        g.mu.Store(key, c)

        // 创建一个新的goroutine来获取数据
        go func() {
            defer c.wg.Done()

            // 获取数据
            c.val, c.err = fn()
        }()
    }

    // 等待goroutine完成数据获取操作
    c.wg.Wait()

    // 返回获取到的数据和错误
    return c.val, c.err
}

Singleflight使用过程中的“坑”

在使用Singleflight的过程中,可能会遇到一些“坑”。下面我们将介绍一些常见的“坑”以及如何避免这些“坑”。

1. 数据获取函数中使用外部变量

在Singleflight的数据获取函数中,如果使用了外部变量,则需要小心处理该变量的并发访问问题。因为在并发场景下,多个goroutine可能会同时执行数据获取函数,从而导致外部变量出现数据竞争问题。

为了避免这种情况,可以将外部变量复制一份到数据获取函数中,或者使用sync.Mutex等锁机制来保护外部变量的并发访问。

2. 数据获取函数中执行耗时操作

在Singleflight的数据获取函数中,如果执行了耗时操作,则可能会导致后续的并发请求长时间等待。为了避免这种情况,可以将耗时操作放到 goroutine中执行,并在数据获取函数中等待goroutine的返回结果。

3. 数据获取函数中发生错误

在Singleflight的数据获取函数中,如果发生了错误,则后续的并发请求都会收到相同的错误。为了避免这种情况,可以对数据获取函数中的错误进行处理,并只将处理后的结果返回给后续的并发请求。

结语

Singleflight是一个非常有用的并发控制库,它可以帮助我们轻松处理共享数据访问的场景。通过本文的介绍,相信你已经对Singleflight有了一定的了解。希望你能够在实际项目中使用Singleflight来优化你的程序性能。