返回顶部
首页 > 资讯 > 精选 >Go singleflight如何使用
  • 920
分享到

Go singleflight如何使用

2023-07-04 22:07:29 920人浏览 八月长安
摘要

本篇内容介绍了“Go singleflight如何使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!在使用它之前我们需要导包:&n

本篇内容介绍了“Go singleflight如何使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

在使用它之前我们需要导包:

 go get golang.org/x/sync/singleflight

golang/sync/singleflight.Group 是 Go 语言扩展包中提供了另一种同步原语,它能够在一个服务中抑制对下游的多次重复请求。一个比较常见的使用场景是:我们在使用 Redis数据库中的数据进行缓存,发生缓存击穿时,大量的流量都会打到数据库上进而影响服务的尾延时。

Go singleflight如何使用

但是 golang/sync/singleflight.Group 能有效地解决这个问题,它能够限制对同一个键值对的多次重复请求,减少对下游的瞬时流量。

Go singleflight如何使用

使用方法

singleflight类的使用方法就新建一个singleflight.Group,使用其方法Do或者DoChan来包装方法,被包装的方法在对于同一个key,只会有一个协程执行,其他协程等待那个协程执行结束后,拿到同样的结果。

Group结构体

代表一类工作,同一个group中,同样的key同时只能被执行一次

Do方法

func (g *Group) Do(key string, fn func() (interface{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->}, error)) (v interface{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->}, err error, shared bool)

key:同一个key,同时只有一个协程执行

fn:被包装的函数

v:返回值,即执行结果。其他等待的协程都会拿到

shared:表示是否由其他协程得到了这个结果v

DoChan方法

func (g *Group) DoChan(key string, fn func() (interface{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->}, error)) <-chan Result

Do差不多其实,因此我们就只讲解Do的实际应用场景了。

具体应用场景

var singleSetCache singleflight.Groupfunc GetAndSetCache(r *Http.Request, cacheKey string) (string, error) {log.Printf("request %s start to get and set cache...", r.URL)value, err, _ := singleSetCache.Do(cacheKey, func() (interface{}, error) {log.Printf("request %s is getting cache...", r.URL)time.Sleep(3 * time.Second)log.Printf("request %s get cache success!", r.URL)return cacheKey, nil})return value.(string), err}func main() {r := gin.Default()r.GET("/sekill/:id", func(context *gin.Context) {ID := context.Param("id")cache, err := GetAndSetCache(context.Request, ID)if err != nil {log.Println(err)}log.Printf("request %s get value: %v", context.Request.URL, cache)})r.Run()}

来看一下执行结果:

2022/12/29 16:21:18 request /sekill/5 start to get and set cache...
2022/12/29 16:21:18 request /sekill/5 is getting cache...
2022/12/29 16:21:18 request /sekill/9 start to get and set cache...
2022/12/29 16:21:18 request /sekill/9 is getting cache...
2022/12/29 16:21:18 request /sekill/9 start to get and set cache...
2022/12/29 16:21:18 request /sekill/5 start to get and set cache...
2022/12/29 16:21:18 request /sekill/5 start to get and set cache...
2022/12/29 16:21:18 request /sekill/9 start to get and set cache...
2022/12/29 16:21:18 request /sekill/9 start to get and set cache...
2022/12/29 16:21:18 request /sekill/5 start to get and set cache...
2022/12/29 16:21:19 request /sekill/9 start to get and set cache...
2022/12/29 16:21:19 request /sekill/5 start to get and set cache...
2022/12/29 16:21:21 request /sekill/9 get cache success!
2022/12/29 16:21:21 request /sekill/5 get cache success!
2022/12/29 16:21:21 request /sekill/5 get value: 5
2022/12/29 16:21:21 request /sekill/5 get value: 5
[GIN] 2022/12/29 - 16:21:21 | 200 |    3.0106529s |       127.0.0.1 | GET      "/sekill/5"
2022/12/29 16:21:21 request /sekill/9 get value: 9
[GIN] 2022/12/29 - 16:21:21 | 200 |    2.8090881s |       127.0.0.1 | GET      "/sekill/5"
2022/12/29 16:21:21 request /sekill/9 get value: 9
[GIN] 2022/12/29 - 16:21:21 | 200 |    2.2166003s |       127.0.0.1 | GET      "/sekill/9"
2022/12/29 16:21:21 request /sekill/9 get value: 9
[GIN] 2022/12/29 - 16:21:21 | 200 |    2.6064069s |       127.0.0.1 | GET      "/sekill/9"
2022/12/29 16:21:21 request /sekill/9 get value: 9
[GIN] 2022/12/29 - 16:21:21 | 200 |    2.4178652s |       127.0.0.1 | GET      "/sekill/9"
2022/12/29 16:21:21 request /sekill/9 get value: 9
[GIN] 2022/12/29 - 16:21:21 | 200 |    2.8101267s |       127.0.0.1 | GET      "/sekill/9"
2022/12/29 16:21:21 request /sekill/5 get value: 5
[GIN] 2022/12/29 - 16:21:21 | 200 |    3.0116892s |       127.0.0.1 | GET      "/sekill/9"
2022/12/29 16:21:21 request /sekill/5 get value: 5
[GIN] 2022/12/29 - 16:21:21 | 200 |    2.6074537s |       127.0.0.1 | GET      "/sekill/5"
2022/12/29 16:21:21 request /sekill/5 get value: 5
[GIN] 2022/12/29 - 16:21:21 | 200 |    2.4076473s |       127.0.0.1 | GET      "/sekill/5"
[GIN] 2022/12/29 - 16:21:21 | 200 |     2.218686s |       127.0.0.1 | GET      "/sekill/5"

可以看到确实只有一个协程执行了被包装的函数,并且其他协程都拿到了结果。

接下来我们来看一下它的原理吧!

原理

首先来看一下Group结构体:

type Group struct {   mu sync.Mutex  // 保证并发安全      m  map[string]*call //保存key对应的函数执行过程和结果的变量。}

然后我们来看一下call结构体:

type call struct {    wg sync.WaitGroup //用WaitGroup实现只有一个协程执行函数    val interface{} //函数执行结果    err error    forgotten bool    dups  int  //含义是duplications,即同时执行同一个key的协程数量    chans []chan<- Result}

然后我们来看一下Do方法:

func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {    // 写Group的m字段时,加锁保证写安全g.mu.Lock()if g.m == nil {g.m = make(map[string]*call)}if c, ok := g.m[key]; ok {        // 如果key已经存在,说明已经由协程在执行,则dups++并等待其执行结果,执行结果保存在对应的call的val字段里c.dups++g.mu.Unlock()c.wg.Wait()if e, ok := c.err.(*panicError); ok {panic(e)} else if c.err == errGoexit {runtime.Goexit()}return c.val, c.err, true}    // 如果key不存在,则新建一个call,并使用WaitGroup来阻塞其他协程,同时在m字段里写入key和对应的callc := new(call)c.wg.Add(1)g.m[key] = cg.mu.Unlock()g.doCall(c, key, fn) // 进来的第一个协程就来执行这个函数return c.val, c.err, c.dups > 0}

然后我们来分析一下doCall函数:

func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {c.val, c.err = fn()c.wg.Done()g.mu.Lock()delete(g.m, key)for _, ch := range c.chans {ch <- Result{c.val, c.err, c.dups > 0}}g.mu.Unlock()}
  • 运行传入的函数 fn,该函数的返回值会赋值给 c.valc.err

  • 调用 sync.WaitGroup.Done 方法通知所有等待结果的 Goroutine &mdash; 当前函数已经执行完成,可以从 call 结构体中取出返回值并返回了;

  • 获取持有的互斥锁并通过管道将信息同步给使用 golang/sync/singleflight.Group.DoChan 方法的 Goroutine

问题分析

分析了源码之后,我们得出了一个结论,这个东西是用阻塞来实现的,这就引发了一个问题:如果我们处理的那个请求刚好遇到问题了,那么后面的所有请求都会被阻塞,也就是,我们应该加上适合的超时控制,如果在一定时间内,没有获得结果,那么就当作超时处理。

于是这个适合我们应该使用DoChan()。两者实现上完全一样,不同的是, DoChan() 通过 channel 返回结果。因此可以使用 select 语句实现超时控制。

var singleSetCache singleflight.Groupfunc GetAndSetCache(r *http.Request, cacheKey string) (string, error) {   log.Printf("request %s start to get and set cache...", r.URL)   retChan := singleSetCache.DoChan(cacheKey, func() (interface{}, error) {      log.Printf("request %s is getting cache...", r.URL)      time.Sleep(3 * time.Second)      log.Printf("request %s get cache success!", r.URL)      return cacheKey, nil   })   var ret singleflight.Result   timeout := time.After(2 * time.Second)   select {   case <-timeout:      log.Println("time out!")      return "", errors.New("time out")   case ret = <-retChan: // 从chan中获取结果      return ret.Val.(string), ret.Err   }}func main() {   r := gin.Default()   r.GET("/sekill/:id", func(context *gin.Context) {      ID := context.Param("id")      cache, err := GetAndSetCache(context.Request, ID)      if err != nil {         log.Println(err)      }      log.Printf("request %s get value: %v", context.Request.URL, cache)   })   r.Run()}

补充

这里其实还有一个Forget方法,它可以在映射表中删除某个键,接下来对键的调用就不会等待前面的函数返回了。

“Go singleflight如何使用”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

--结束END--

本文标题: Go singleflight如何使用

本文链接: https://lsjlt.com/news/347780.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Go singleflight如何使用
    本篇内容介绍了“Go singleflight如何使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!在使用它之前我们需要导包:&n...
    99+
    2023-07-04
  • 为什么 docall 在 singleflight 中使用 gopanic?
    php小编香蕉为您解答:为什么docall在singleflight中使用gopanic?在singleflight中,当多个goroutine同时请求相同的任务时,为了避免重复执行,...
    99+
    2024-02-08
    并发请求
  • 如何使用go module
    这篇文章给大家分享的是有关如何使用go module的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。go module 使用go module 在 go 1.14 版本之后被推出一、go module 使用介绍go...
    99+
    2023-06-26
  • Go Callvis如何使用
    今天小编给大家分享一下Go Callvis如何使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Go-callvis是一种g...
    99+
    2023-07-05
  • go pprof如何使用
    这篇文章主要介绍“go pprof如何使用”,在日常操作中,相信很多人在go pprof如何使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”go pprof如何使用”的疑惑有所帮助!接下来,请跟着小编一起来...
    99+
    2023-07-05
  • go中如何使用select
    这篇文章主要为大家展示了“go中如何使用select”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“go中如何使用select”这篇文章吧。golang中的select语句格式如下select&n...
    99+
    2023-06-26
  • GO的range如何使用
    在Go语言中,range关键字用于迭代数组、切片、字符串、映射和通道等数据结构。它提供了一种简洁的遍历方式。使用range关键字的基本语法如下:```gofor index, value := range data {// 循环体}``...
    99+
    2023-08-09
    GO range
  • 在Go中如何使用Json
    小编给大家分享一下在Go中如何使用Json,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!           &...
    99+
    2023-06-22
  • 如何正确使用Go Map
    本篇内容主要讲解“如何正确使用Go Map”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何正确使用Go Map”吧!前言例子如下:func main() { &n...
    99+
    2023-06-15
  • 如何正确使用Go defer
    这篇文章主要介绍“如何正确使用Go defer”,在日常操作中,相信很多人在如何正确使用Go defer问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何正确使用Go defer”的疑惑有所帮助!接下来,请跟...
    99+
    2023-06-15
  • 如何在Go中使用NumPy?
    NumPy是Python中广泛使用的一个科学计算库,它提供了高效的数组运算和数学函数,使得Python在数据科学领域得到了广泛的应用。但是,NumPy并不仅限于Python语言,它也可以被其他编程语言使用。本文将介绍如何在Go语言中使用N...
    99+
    2023-09-08
    numpy apache 二维码
  • Go语言包如何使用
    本篇内容介绍了“Go语言包如何使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!包(package)是多个Go源码的集合,是一种高级的代码复...
    99+
    2023-07-04
  • Go slice切片如何使用
    这篇文章主要介绍“Go slice切片如何使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Go slice切片如何使用”文章能帮助大家解决问题。定义切片区别于数组,是引用类型,...
    99+
    2023-07-02
  • go redis之redigo如何使用
    今天小编给大家分享一下go redis之redigo如何使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。安装go...
    99+
    2023-06-30
  • 如何使用go连接clickhouse
    这篇文章主要介绍“如何使用go连接clickhouse”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“如何使用go连接clickhouse”文章能帮助大家解决问题。近段时间业务在一个局点测试click...
    99+
    2023-07-05
  • Go语言sync.Cond如何使用
    本篇内容介绍了“Go语言sync.Cond如何使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!基本使用1 定义sync.Cond是Go语言...
    99+
    2023-07-05
  • 如何在 JavaScript 中使用 Go?
    随着 Web 应用程序的复杂性不断增加,Web 开发人员不断寻找更好的方式来构建更高效的应用程序。其中一种最新的趋势是将服务器端代码从传统的语言(如 PHP、Ruby 或 Python)转移到更快、更安全、更强大的语言中。 其中一种语言是...
    99+
    2023-08-22
    npm 函数 javascript
  • 如何使用Go构建Kubernetes应用
    这篇文章主要介绍“如何使用Go构建Kubernetes应用”,在日常操作中,相信很多人在如何使用Go构建Kubernetes应用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何使用Go构建Kubernete...
    99+
    2023-06-15
  • Go语言中error如何使用
    这期内容当中小编将会给大家带来有关Go语言中error如何使用,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1. error介绍error其实就是实现了Error()函数...
    99+
    2024-04-02
  • Go语言中Once如何使用
    Go语言中Once如何使用,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。sync.Once是sync包中的一个对象,它只有一个...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作