Go singleflight库源码分析

设计思想

singleflight是Go语言中的一个开源库;主要作用的是避免对同一个任务的重复请求。

它的设计思想主要是通过一个map和一个互斥锁来实现对任务请求的管理。

// Group 一个工作单元,可以管理多个singleFlight任务
type Group struct {mu sync.Mutex       // protects mm  map[string]*call // lazily initialized
}

map中的每一个key对应一个call,call中保存了请求的结果和一个channel,用于阻塞和唤醒请求;

当一个新的请求来时,首先会通过map查找是否有相同的请求正在进行,如果有则等待,如果没有则新建一个请求并保存到map中。

// call 任务单次执行时,会提前创建call结构,用于任务执行、阻塞相同key的请求
type call struct {wg sync.WaitGroup// 任务返回结果,在任务执行完成后写入val interface{}err error// 重复的任务数dups  int// 异步任务执行结束后,会遍历chans通知结果chans []chan<- Result
}

调用流程

当调用singleflight的Do方法时,首先会检查是否有相同key的请求正在进行,如果有则等待,如果没有则新建一个请求。

请求点执行方式有两种,根据调用方式的不同分为 同步、异步请求。

Do:结果保存在call.val中,调用后会阻塞,直到单次任务执行完成;

DoChan:调用后返回一个ch,任务执行完成后将结果通知到ch中。

解决了哪些问题

singleFlight主要解决了对同一个任务的重复执行问题;如

  • 缓存击穿,redis缓存过期,大量请求同时到达服务器,导致数据库访问量增高;
  • 配置加载,并发情况下拉取资源,本地缓存还没有生效,多个请求都会从远程服务获取资源;

通过singleflight,可以保证对同一个任务的请求在同一时间只有一个,避免了一些耗时较长任务的重复执行。

什么场景下不适合使用

虽然singleflight可以有效避免对同一个任务的重复请求,但是在一些场景下可并不适合,如

  • 任务查询的效率很高,而且调用频率不高,那使用singleFlight会带来额外的加锁、解锁开销;
  • 请求要求实时返回,使用singleflight会导致一些请求阻塞,然后集中返回,导致部分请求响应时间长,而且返回的瞬间会有瞬时的高峰。

使用示例

var group = singleflight.Group{}func GetSceneConfig(name string) *SceneConfig {uniqKey := fmt.Sprintf("uniq_key:%s", name)group.Do(uniqKey, func() (interface{}, error) {return sceneMapper.GetConfig(name)})
}

源码展示

// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.// Package singleflight provides a duplicate function call suppression
// mechanism.
package singleflight // import "golang.org/x/sync/singleflight"import ("bytes""errors""fmt""runtime""runtime/debug""sync"
)// errGoexit indicates the runtime.Goexit was called in
// the user given function.
var errGoexit = errors.New("runtime.Goexit was called")// panicError 在执行给定任务过程中发生panic异常时,抛出的err类型
type panicError struct {value interface{}stack []byte
}// Error implements error interface.
func (p *panicError) Error() string {return fmt.Sprintf("%v\n\n%s", p.value, p.stack)
}func (p *panicError) Unwrap() error {err, ok := p.value.(error)if !ok {return nil}return err
}func newPanicError(v interface{}) error {stack := debug.Stack()// The first line of the stack trace is of the form "goroutine N [status]:"// but by the time the panic reaches Do the goroutine may no longer exist// and its status will have changed. Trim out the misleading line.if line := bytes.IndexByte(stack[:], '\n'); line >= 0 {stack = stack[line+1:]}return &panicError{value: v, stack: stack}
}// call 任务单次执行时,会提前创建call结构,用户任务执行与阻塞
type call struct {wg sync.WaitGroup// 任务返回结果,在任务执行完成后写入val interface{}err error// 重复的任务数dups  intchans []chan<- Result
}// Group 一个工作单元,可以管理多个singleFlight任务
type Group struct {mu sync.Mutex       // protects mm  map[string]*call // lazily initialized
}// Result holds the results of Do, so they can be passed
// on a channel.
type Result struct {Val    interface{}Err    errorShared bool
}// Do singleFlight任务执行入口,在任务没有执行过程中多次重复key的调用会被阻塞等待第一次任务执行完成才返回。
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {g.mu.Lock()if g.m == nil {g.m = make(map[string]*call)}if c, ok := g.m[key]; ok {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}c := 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
}// DoChan 异步执行单次任务,执行完成后,把结果通过ch的方式返回。
func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result {ch := make(chan Result, 1)g.mu.Lock()if g.m == nil {g.m = make(map[string]*call)}if c, ok := g.m[key]; ok {c.dups++c.chans = append(c.chans, ch)g.mu.Unlock()return ch}c := &call{chans: []chan<- Result{ch}}c.wg.Add(1)g.m[key] = cg.mu.Unlock()go g.doCall(c, key, fn)return ch
}// doCall 任务在这里真正执行,结果会写入到c.val中,如果[]chan有值,也会依次发送。
func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {normalReturn := falserecovered := false// use double-defer to distinguish panic from runtime.Goexit,// more details see https://golang.org/cl/134395defer func() {// the given function invoked runtime.Goexitif !normalReturn && !recovered {c.err = errGoexit}g.mu.Lock()defer g.mu.Unlock()c.wg.Done()if g.m[key] == c {delete(g.m, key)}if e, ok := c.err.(*panicError); ok {// In order to prevent the waiting channels from being blocked forever,// needs to ensure that this panic cannot be recovered.if len(c.chans) > 0 {go panic(e)select {} // Keep this goroutine around so that it will appear in the crash dump.} else {panic(e)}} else if c.err == errGoexit {// Already in the process of goexit, no need to call again} else {// Normal returnfor _, ch := range c.chans {ch <- Result{c.val, c.err, c.dups > 0}}}}()func() {defer func() {if !normalReturn {// Ideally, we would wait to take a stack trace until we've determined// whether this is a panic or a runtime.Goexit.//// Unfortunately, the only way we can distinguish the two is to see// whether the recover stopped the goroutine from terminating, and by// the time we know that, the part of the stack trace relevant to the// panic has been discarded.if r := recover(); r != nil {c.err = newPanicError(r)}}}()c.val, c.err = fn()normalReturn = true}()if !normalReturn {recovered = true}
}// 忽略key对应的之前到达的key,调用Forget之后,下次传入key,对应的任务会重新执行。
func (g *Group) Forget(key string) {g.mu.Lock()delete(g.m, key)g.mu.Unlock()
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/495803.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【更新】Docker新手入门教程2:在Windows系统通过compose创建多个mysql镜像并配置应用

文章目录 前言一、运行Docker init生成docker配置文件二、修改创建镜像的配置文件1、添加镜像挂载点 三、【拉取镜像】四、生成Docker 镜像查看生成的镜像 五、修改Compose配置文件3、配置Mysql六、生成Docker容器七、检查容器创建状态总结 前言 在window下通过Docker创建mysq…

lxml 解析xml\html

from lxml import etree# XML文档示例 xml_doc """ <root><book><title>Python编程指南</title><author>张三</author></book><book><title>Python高级编程</title><author>李四</autho…

FFmpeg在python里推流被处理过的视频流

链式算法处理视频流 视频源是本地摄像头 # codinggbk # 本地摄像头直接推流到 RTMP 服务器 import cv2 import mediapipe as mp import subprocess as sp# 初始化 Mediapipe mp_drawing mp.solutions.drawing_utils mp_drawing_styles mp.solutions.drawing_styles mp_holis…

从零开始k8s-部署篇(未完待续)

从零开始k8s 1.部署k8s-部署篇 1.部署k8s-部署篇 本次部署完全学习于华子的博客点击此处进入华子主页 K8S中文官网&#xff1a;https://kubernetes.io/zh-cn 笔者从零开始部署的k8s&#xff0c;部署前置条件为 1.需要harbor仓库&#xff0c;存放镜像&#xff0c;拉取镜像&am…

Pytorch | 利用AI-FGTM针对CIFAR10上的ResNet分类器进行对抗攻击

Pytorch | 利用AI-FGTM针对CIFAR10上的ResNet分类器进行对抗攻击 CIFAR数据集AI-FGTM介绍算法流程初始化迭代更新&#xff08; t 0 t 0 t0 到 T − 1 T - 1 T−1&#xff09;迭代完成 AI-FGTM代码实现AI-FGTM算法实现攻击效果 代码汇总aifgtm.pytrain.pyadvtest.py 之前已经…

视频监控平台:Liveweb视频汇聚融合平台智慧安防视频监控应用方案

Liveweb是一款功能强大、灵活部署的安防视频监控平台&#xff0c;支持多种主流标准协议&#xff0c;包括GB28181、RTSP/Onvif、RTMP等&#xff0c;同时兼容海康Ehome、海大宇等厂家的私有协议和SDK接入。该平台不仅提供传统安防监控功能&#xff0c;还支持接入AI智能分析&#…

【Linux系列】Shell 脚本中的条件判断:`[ ]`与`[[ ]]`的比较

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【LLM论文日更】| 训练大型语言模型在连续潜在空间中进行推理

论文&#xff1a;https://arxiv.org/pdf/2412.06769代码&#xff1a;暂未开源机构 &#xff1a;Meta领域&#xff1a;思维链发表&#xff1a;arxiv 研究背景 研究问题&#xff1a;这篇文章要解决的问题是如何在大语言模型&#xff08;LLMs&#xff09;中实现一种新的推理范式&…

opc da 服务器数据 转 opc ua项目案例

目录 1 案例说明 2 VFBOX网关工作原理 3 应用条件 4 查看OPC DA服务器的相关参数 5 配置网关采集opc da数据 6 用opc ua协议转发采集的数据 7 在服务器上运行仰科OPC DA采集软件 8 案例总结 1 案例说明 在OPC DA服务器上运行OPC DA client软件查看OPC DA服务器的相关参…

05.HTTPS的实现原理-HTTPS的握手流程(TLS1.2)

05.HTTPS的实现原理-HTTPS的握手流程&#xff08;TLS1.2&#xff09; 简介1. TLS握手过程概述2. TLS握手过程细化3. 主密钥&#xff08;对称密钥&#xff09;生成过程4. 密码规范变更 简介 主要讲述了混合加密流程完成后&#xff0c;客户端和服务器如何共同获得相同的对称密钥…

Excel粘贴复制不完整的原因以及解决方法

在数据处理和分析的过程中&#xff0c;Excel无疑是不可或缺的工具。然而&#xff0c;在使用Excel进行复制粘贴操作时&#xff0c;有时会遇到粘贴不完整的情况&#xff0c;这可能会让人感到困惑和烦恼。本文将深入探讨Excel粘贴复制不完整的原因、提供解决方案&#xff0c;并给出…

数据中台从centos升级为国产操作系统后,资源增加字段时,提交报500错误

文章目录 背景一、步骤1.分析阶段2.查看nginx3.修改用户&#xff08;也可以修改所有者权限&#xff09; 背景 故障报错&#xff1a; nginx报错信息&#xff1a; 2024/12/19 15:25:31 [crit, 500299#0: *249 onen0 " /var/lib/nginx/tmp/cient body/0000000001" f…

在Windows11上编译C#的实现Mono的步骤

在Windows11上编译Mono的步骤 1、 在win11打开开发者模式,在更新和安全选项里,如下图: 2、下载并安装64位的cygwin, 下载网站:www.cygwin.com 3、 安装 Visual Studio 2015 or later 的社区版本。 4、 下载Mono的windows最新版本。 5、 在cmd.exe里运行下面的命令来安…

我的创作纪念日(五年)

慕然回首 平平无奇的周一早晨&#xff0c;收到来自csdn的提醒&#xff0c;创作纪念日五周年了&#xff0c;这也意味着我从事开发行业差不多有整整五年了&#xff0c;五年啊&#xff01;你知道这五年我是怎么过的吗&#xff1f;一句Just do IT&#xff0c;我做it整整做了五年&am…

python+reportlab创建PDF文件

目录 字体导入 画布写入 创建画布对象 写入文本内容 写入图片内容 新增页 画线 表格 保存 模板写入 创建模板对象 段落及样式 表格及样式 画框 图片 页眉页脚 添加图形 构建pdf文件 reportlab库支持创建包含文本、图像、图形和表格的复杂PDF文档。 安装&…

人工智能ACA(七)——计算机视觉基础

一、自然语言处理基本介绍 1. 自然语言处理的定义 1-1 自然语言 人类使用的在社会生活中自然形成的语言 1-2 自然语言处理 目标是让计算机能够理解、解析、生成和处理人类的自然语言 包含自然语言理解和自然语言生成两部分组成 2. 自然语言处理的发展趋势 3.自然语言处理…

Ubuntu20.04 交叉编译Qt5.15.15 for rk3588

rk3588编译Qt搞了我大半年了&#xff0c;一直困惑特别鸣谢&#xff1a;qq1033878279的网友远程帮我编译演示了一遍。 一、vmware 安装基础工具 sudo apt install -y build-essential net-tools openssh-server vim openssl libssl-dev 二、vmware 下载 cmake和Qt源码 下载cm…

使用开源在线聊天工具Fiora轻松搭建个性化聊天平台在线交流

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff1a;人工智能教程 文章目录 前言1.关于Fiora2.安装Docker3.本地部署Fiora4.使用Fiora5.cpolar内网穿透工具安装6.创建远程连接公网地址7.固定Uptime …

DDoS防护中的流量清洗与智能调度

DDoS防护中的流量清洗与智能调度有哪些好处 在数字化高度发展的今天&#xff0c;企业依赖于互联网进行业务运营&#xff0c;而网络安全威胁也随之增加。其中&#xff0c;DDoS&#xff08;分布式拒绝服务&#xff09;攻击是一种常见且破坏性极强的网络攻击手段。为了有效应对DDo…

“乡村探索者”:村旅游网站的移动应用开发

3.1 可行性分析 从三个不同的角度来分析&#xff0c;确保开发成功的前提是有可行性分析&#xff0c;只有进行提前分析&#xff0c;符合程序开发流程才不至于开发过程的中断。 3.1.1 技术可行性 在技术实现层次&#xff0c;分析了好几种技术实现方法&#xff0c;并且都有对应的成…