golang,go,博客,开源,编程

golang每日一库之singleflight

Published on with 0 views and 0 comments

singleflight 是 Go 标准库之外的一个常用库,属于 Go内置库 golang.org/x/sync 中的一个包。它的主要作用是 防止多个 goroutine 对同一资源进行重复计算或请求,避免了资源的重复计算,提高了性能和效率。这个库最常见的应用场景是,多个并发请求可能会同时去获取相同的资源或者计算同样的结果,我们希望只有一个请求会真正去处理,其他请求直接复用这个结果。

1. 基本原理

singleflight 通过一个共享的锁机制来保证,多个 goroutine 在请求相同资源时,只有第一个请求会实际执行计算或者请求,其他请求会等待第一个请求完成后返回相同的结果。换句话说,它确保在并发环境中,对于同一个键,只有一个请求会被发送,而其他并发请求会共享同一个计算结果。

2. 典型使用场景

  • 缓存穿透问题:假设你有一个缓存,如果某个请求数据不存在于缓存中,就会从数据库或者外部服务中获取。多个请求可能会同时请求同样的数据,这时就可能会导致多个请求同时去访问数据库。使用 singleflight 可以确保只有一个请求去访问数据库,其他请求可以等待并复用相同的数据。
  • 避免重复计算:多个 goroutine 可能同时请求一个计算结果,可以通过 singleflight 保证只有一个请求进行计算,其他请求复用结果。

3. 主要结构和方法

singleflight.Group

Groupsingleflight 包中最主要的结构,它提供了防止重复请求的功能。Group 中有两个主要的方法:

  • Do:此方法用于发起计算,接收一个键和一个回调函数。每个键对应的回调函数仅会执行一次,其他请求会等待并复用该回调的结果。
package singleflight

import "sync"

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

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

Group.Do(key string, fn func() (interface{}, error))

这个方法会执行以下逻辑:

  • 如果 key 对应的请求尚未执行过(即没有 goroutine 在处理),那么会调用 fn 来计算并返回结果。
  • 如果已经有 goroutine 正在处理这个 key,其他的 goroutine 会等待这个 goroutine 完成计算,等待结果并返回。

返回的 interface{} 可以是任何类型,error 用于表示计算过程中是否出错。

4. 代码示例

以下是使用 singleflight 库的一个简单示例,模拟多个 goroutine 对同一资源进行并发请求的情况:

package main

import (
	"fmt"
	"golang.org/x/sync/singleflight"
	"time"
)

var g singleflight.Group

// 模拟一个耗时的计算
func expensiveComputation() (interface{}, error) {
	fmt.Println("Performing expensive computation...")
	time.Sleep(2 * time.Second) // 模拟计算耗时
	return "result", nil
}

func main() {
	// 模拟多个 goroutine 进行相同的计算请求
	var wg sync.WaitGroup
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			// 每个 goroutine 都发起请求
			result, err, _ := g.Do("key", func() (interface{}, error) {
				return expensiveComputation()
			})

			if err != nil {
				fmt.Printf("Goroutine %d: Error: %v\n", id, err)
				return
			}

			fmt.Printf("Goroutine %d: Got result: %v\n", id, result)
		}(i)
	}

	// 等待所有 goroutine 完成
	wg.Wait()
}

5. 执行过程解释

  • 我们有 3 个 goroutine,并且它们都请求相同的 key ("key"),并且执行一个模拟的 expensiveComputation 函数。每个 goroutine 都调用 g.Do
  • 第一个 goroutine 会执行 expensiveComputation 并返回结果。其他两个 goroutine 会等待第一个 goroutine 完成,并复用它的结果。
  • 因此,尽管有多个并发请求,只有一个请求会真正执行计算,其他请求会共享结果。

6. Do 方法的返回值

Do 方法返回的是:

  • interface{}:计算的结果。
  • error:如果计算过程中发生错误,返回错误。
  • 还有一个值用于标识此计算是否已经被执行过并正在进行中。

7. 优点与使用场景

  • 减少重复请求:适合用于避免重复的外部请求,比如频繁访问数据库或外部 API。
  • 缓存机制:可以与缓存结合使用,减少不必要的计算,尤其是在处理高并发时。
  • 计算复用:避免多次进行相同的计算或耗时的操作,提升系统性能。

8. 适用场景

singleflight 适合以下场景:

  1. 缓存穿透:多个并发请求可能会查询相同的数据,导致重复的计算或外部请求。使用 singleflight 可以确保只发起一次查询,其他请求复用结果。
  2. 重复计算:多个 goroutine 可能同时请求相同的计算结果,singleflight 可以避免重复计算,节省系统资源。
  3. 外部 API 请求:当多个请求需要访问相同的外部 API 时,可以通过 singleflight 防止重复调用相同的 API。

9. 总结

singleflight 是 Go 语言中一个非常实用的库,尤其是在处理并发和性能优化时非常有效。它通过 Group 结构和 Do 方法,避免了对同一资源的重复计算或请求,减少了重复的网络请求或计算,进而提升了程序的性能和效率。在高并发场景中,可以有效减轻系统压力。


标题:golang每日一库之singleflight
作者:mooncakeee
地址:http://blog.dd95828.com/articles/2025/02/14/1739500137298.html
联系:scotttu@163.com