Go singleflight库源码分析

设计思想

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

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

// Group 一个工作单元,可以管理多个singleFlight任务
type Group struct {
	mu sync.Mutex       // protects m
	m  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  int
	chans []chan<- Result
}

// Group 一个工作单元,可以管理多个singleFlight任务
type Group struct {
	mu sync.Mutex       // protects m
	m  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    error
	Shared 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] = c
	g.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] = c
	g.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 := false
	recovered := false

	// use double-defer to distinguish panic from runtime.Goexit,
	// more details see https://golang.org/cl/134395
	defer func() {
		// the given function invoked runtime.Goexit
		if !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 return
			for _, 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()
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【iOS安全】Block开发与逆向

1. OC中的Block 1.1 Block的基本概念 在iOS开发中&#xff0c;Block是一种特殊的数据类型&#xff0c;类似于其他编程语言中的匿名函数。它可以封装一段代码&#xff0c;并且能够像普通变量一样传递、存储和执行。Block可以捕获并访问定义它时所在作用域的变量&#xff0c;这…

C# 中的记录类型简介 【代码之美系列】

&#x1f380;&#x1f380;&#x1f380;代码之美系列目录&#x1f380;&#x1f380;&#x1f380; 一、C# 命名规则规范 二、C# 代码约定规范 三、C# 参数类型约束 四、浅析 B/S 应用程序体系结构原则 五、浅析 C# Async 和 Await 六、浅析 ASP.NET Core SignalR 双工通信 …

查询 MySQL 默认的存储引擎(SELECT @@default_storage_engine;)

要查询 MySQL 默认的存储引擎&#xff0c;可以使用以下 SQL 查询语句&#xff1a; SELECT default_storage_engine;解释&#xff1a; SELECT: 表示你要执行一个查询。default_storage_engine: 这是一个 MySQL 系统变量&#xff0c;它存储着当前 MySQL 服务器的默认存储引擎。…

大数据技术-Hadoop(二)HDFS的介绍与使用

目录 1、HDFS简介 1.1 什么是HDFS 1.2 HDFS的优点 1.3、HDFS的架构 1.3.1、 NameNode 1.3.2、 NameNode的职责 1.3.3、DataNode 1.3.4、 DataNode的职责 1.3.5、Secondary NameNode 1.3.6、Secondary NameNode的职责 2、HDFS的工作原理 2.1、文件存储 2.2 、数据写…

SpringBoot项目的5种搭建方式(以idea2017为例)

目录 1. idea中使用官方API 2. idea中使用阿里云API 3. 在spring官网创建 4. 在阿里云官网创建 5. Maven项目改造成springboot项目 SpringBoot项目的创建细分一共有5种&#xff0c;其实主要分为以下三种&#xff1a; ①使用开发工具idea创建springboot项目&#xff08; Sp…

Android 设置铃声和闹钟

Android设置铃声和闹钟使用的方法是一样的&#xff0c;但是要区别的去获取对应的权限。 统一权限&#xff0c;不管是设置闹钟还是铃声&#xff0c;他们都需要一个系统设置权限如下: //高版本需要WRITE_SETTINGS权限//此权限是敏感权限&#xff0c;无法动态申请&#xff0c;需要…

三维扫描在汽车/航空行业应用

三维扫描技术应用范围广泛&#xff0c;从小型精密零件到大型工业设备&#xff0c;都能实现快速、准确的测量。 通过先进三维扫描技术获取产品和物体的形面三维数据&#xff0c;建立实物的三维图档&#xff0c;满足各种实物3D模型数据获取、三维数字化展示、3D多媒体开发、三维…

optuna和 lightgbm

文章目录 optuna使用1.导入相关包2.定义模型可选参数3.定义训练代码和评估代码4.定义目标函数5.运行程序6.可视化7.超参数的重要性8.查看相关信息9.可视化的一个完整示例10.lightgbm实验 optuna使用 1.导入相关包 import torch import torch.nn as nn import torch.nn.functi…

【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?

文章目录 前言问题描述问题分析问题解决1.允许所有用户上传驱动文件2.如果是想只上传白名单的驱动 前言 该方法适合永洪BI系列产品&#xff0c;包括不限于vividime desktop&#xff0c;vividime z-suit&#xff0c;vividime x-suit产品。 问题描述 当我们连接数据源的时候&a…

[项目][boost搜索引擎#4] cpp-httplib使用 log.hpp 前端 测试及总结

目录 编写http_server模块 1. 引入cpp-httplib到项目中 2. cpp-httplib的使用介绍 3. 正式编写http_server 九、添加日志到项目中 十、编写前端模块 十一. 详解传 gitee 十二、项目总结 项目的扩展 写在前面 [项目详解][boost搜索引擎#1] 概述 | 去标签 | 数据清洗 |…

项目练习:若依系统的svg-icon功能实现

文章目录 一、svg图片准备二、自定义Svg组件三、svg插件开发四、Svg组件使用 一、svg图片准备 src/assets/icons/svg 其中svg目录里&#xff0c;存放了所需要的图片 index.js import Vue from vue import SvgIcon from /components/SvgIcon// svg component// register glob…

水库大坝三维模型的开发和使用3Dmax篇

成果图 开发过程 工具插件three.js先加载模型做水体衔接水位测量标尺水位标记断面标记大坝监测点打点 上代码&#xff0c;技术交流V: bloxed <template><div class"box w100 h100"><el-row :gutter"20" v-loading"loading"e…

Win10提示“缺少fbgemm.dll”怎么办?缺失fbgemm.dll文件的修复方法来啦!

fbgemm.dll文件的作用 fbgemm.dll&#xff08;Facebook GEMM library&#xff09;是一个动态链接库文件&#xff0c;它主要用于优化矩阵乘法运算&#xff0c;提高计算性能。虽然它不是Windows 10系统的核心组件&#xff0c;但在某些应用程序或游戏中&#xff0c;尤其是那些需要…

Petalinux使用QSPI FLASH引导启动

目录 1. 预分配Flash空间 1.1 大小估计 1.2 其他注意事项 2. 配置Flash分区 3. 配置各主要文件在Flash中的地址范围 4. 配置boot.scr的偏移 5. 修改U-Boot环境变量在Flash的偏移量 6. 配置设备树中的Flash 7. 开启对EXT4分区管理的支持(根据需要) 8. 编译u-boot 9.…

Android——自定义按钮button

项目中经常高频使用按钮&#xff0c;要求&#xff1a;可设置颜色&#xff0c;有圆角且有按下效果的Button 一、自定义按钮button button的代码为 package com.fslihua.clickeffectimport android.annotation.SuppressLint import android.content.Context import android.gra…

黑龙江等保测评费用怎么收?

‌黑龙江二级等保测评费用‌&#xff1a;费用区间大致在3万至6万人民币之间&#xff0c;具体费用取决于测评机构的定价策略、所提供的服务内容以及企业的实际需求‌&#xff0c;服务内容包括防火墙、Web应用防火墙(WAF)、堡垒机、日志审计、漏洞扫描以及等保安全整改等‌。 ‌…

中文拼写检测纠正 Read, Listen, and See Leveraging Multimodal Information 论文

拼写纠正系列 NLP 中文拼写检测实现思路 NLP 中文拼写检测纠正算法整理 NLP 英文拼写算法&#xff0c;如果提升 100W 倍的性能&#xff1f; NLP 中文拼写检测纠正 Paper java 实现中英文拼写检查和错误纠正&#xff1f;可我只会写 CRUD 啊&#xff01; 一个提升英文单词拼…

vue2 elementui if导致的rules判断失效

优化目标 和 目标转化出价必填的 切换的时候还会隐藏掉 这时候的if语句会导致rules判断失效 我的办法是把判断拉到外面 别放在el-form-item里 <section v-if"unitForm.baseTarget OCPM && unitForm.cpaTargetOptions ! undefined && unitForm.cpaTa…

前端(Ajax)

1.客户端请求 向https://jsonplaceholder.typicode.com/users发送get请求 const xhr new XMLHttpRequest(); console.log(xhr.readyState); xhr.open(‘get’, ‘https://jsonplaceholder.typicode.com/users’) console.log(xhr.readyState); xhr.send(); console.log(xhr.…

uboot, s5pv210 , main_loop 分析(16)

main_loop 的代码如下&#xff1a; 4443 void main_loop (void)42 {41 #ifndef CONFIG_SYS_HUSH_PARSER E 40 ▎ static char lastcommand[CONFIG_SYS_CBSIZE] { 0, }; ■ Use of undeclared identifier CONFIG_SYS_CBSIZE39 ▎ int len;38 ▎ int rc 1;37 ▎ …