[go语言]-深入理解singleflight
- Posted by 微博@Yangsc_o
- 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
前言
最近从java转到go,来公司第一个开发工作就是对一个资源请求去重复,最终发现这个singleflight这个好东西,分享一下。
singleflight使用场景
- 缓存击穿:缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
- 绝大多数公司都是这么用的
- 请求资源去重复
- 我们的用法,需要改动一行代码。
singleflight 简介
singleflight 在 golang.org/x/sync/singleflight 项目下,对外提供了以下几个方法
//Do方法,传入key,以及回调函数,如果key相同,fn方法只会执行一次,同步等待
//返回值v:表示fn执行结果
//返回值err:表示fn的返回的err
//返回值shared:表示是否是真实fn返回的还是从保存的map[key]返回的,也就是共享的
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
//DoChan方法类似Do方法,只是返回的是一个chan
func (g *Group) DoChan(key string, fn func() (interface{}, error)) <-chan Result {
//暂时未用到:设计Forget 控制key关联的值是否失效,默认以上两个方法只要fn方法执行完成后,内部维护的fn的值也删除(即并发结束后就失效了)
func (g *Group) Forget(key string)
singleflight的使用
从singleflight的test最简单用法
func TestDo(t *testing.T) {
var g Group
// key 可以理解资源的id
v, err, _ := g.Do("key", func() (interface{}, error) {
// do what you want
return "bar", nil
})
if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
t.Errorf("Do = %v; want %v", got, want)
}
if err != nil {
t.Errorf("Do error = %v", err)
}
}
验证并发重复请求
func process(g *Group, t *testing.T, ch chan int, key string) {
for count := 0; count < 10; count++ {
v, err, shared := g.Do(key, func() (interface{}, error) {
time.Sleep(1000 * time.Millisecond)
return "bar", nil
})
t.Log("v = ", v, " err = ", err, " shared =", shared, " ch :", ch, "g ", len(g.m))
if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want {
t.Errorf("Do = %v; want %v", got, want)
}
if err != nil {
t.Errorf("Do error = %v", err)
}
}
ch <- 1
}
func TestDo1(t *testing.T) {
var g Group
channels := make([]chan int, 10)
key := "key"
for i := 0; i < 10; i++ {
channels[i] = make(chan int)
go process(&g, t, channels[i], key)
}
for i, ch := range channels {
<-ch
fmt.Println("routine ", i, "quit!")
}
}
- 结果

singleflight的原理
call
call 用来表示一个正在执行或已完成的函数调用。
// call is an in-flight or completed singleflight.Do call
type call struct {
wg sync.WaitGroup
// These fields are written once before the WaitGroup is done
// and are only read after the WaitGroup is done.
//val和err用来记录fn发放执行的返回值
val interface{}
err error
// forgotten indicates whether Forget was called with this call's key
// while the call was still in flight.
// 用来标识fn方法执行完成之后结果是否立马删除还是保留在singleflight中
forgotten bool
// These fields are read and written with the singleflight
// mutex held before the WaitGroup is done, and are read but
// not written after the WaitGroup is done.
//dups 用来记录fn方法执行的次数
dups int
//用来记录DoChan中调用次数以及需要返回的数据
chans []chan<- Result
}
Group
Group 可以看做是任务的分类。
// Group represents a class of work and forms a namespace in which
// units of work can be executed with duplicate suppression.
type Group struct {
mu sync.Mutex // protects m
m map[string]*call // lazily initialized
}
Do 函数
// Do executes and returns the results of the given function, making
// sure that only one execution is in-flight for a given key at a
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
// The return value shared indicates whether v was given to multiple callers.
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()
return c.val, c.err, true
}
c := new(call)
// 设置forgotten = true, doCall时 不再调用delete(g.m, key)
// c.forgotten = true
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
g.doCall(c, key, fn)
return c.val, c.err, c.dups > 0
}
// doCall handles the single call for a key.
func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
c.val, c.err = fn()
c.wg.Done()
g.mu.Lock()
if !c.forgotten {
delete(g.m, key)
}
for _, ch := range c.chans {
ch <- Result{c.val, c.err, c.dups > 0}
}
g.mu.Unlock()
}
在Do方法中是通过waitgroup来控制的,主要流程如下:
- 在Group中设置了一个map,如果key不存在,则实例化call(用来保存值信息),并将key=>call的对应关系存入map中通过mutex保证了并发安全
- 如果已经在调用中则key已经存在map,则wg.Wait
- 在fn执行结束之后(在doCall方法中执行)执行wg.Done
- 卡在第2步的方法得到执行,返回结果
其他的DoChan方法也是类似的逻辑,只是返回的是一个chan。
参考
你的鼓励也是我创作的动力
[go语言]-深入理解singleflight的更多相关文章
- 深入理解C语言-深入理解指针
关于指针,其是C语言的重点,C语言学的好坏,其实就是指针学的好坏.其实指针并不复杂,学习指针,要正确的理解指针. 指针是一种数据类型 指针也是一种变量,占有内存空间,用来保存内存地址 指针就是告诉编译 ...
- 深入理解C语言-深入理解void
void的字面意思是"无类型",void *则为"无类型指针",void *可以指向任何类型的数据 void含义 void几乎只有注释和限制程序的作用,定义一个 ...
- C语言 指针理解
1.指针 指针全称是指针变量,其实质是C语言的一种变量.这种变量比较特殊,通常他的值会被赋值为某个变量的地址值(p = &a),然后我们可以使用 *p 这样的方式去间接访问p所指向的那个变量. ...
- java语言浅显理解
从厉害的c语言.到经久不衰的java语言.到不太火的安卓和IOS,到当下流行的python,这些都是软件开发中的一员. 之前在传智播客上的免费视频资源上,听了老师对java语言的介绍,感觉挺好了.今天 ...
- js的语言的理解
1.所谓字面量,就是语言语法 2.在js编译器读到语法时候,执行时候创建对象:在赋值的时候创建一个对象,或者是一个匿名对象. 3.函数定义本身是一个对象:执行时候不产生实例对象:这跟python类不一 ...
- 信号量和互斥量C语言示例理解线程同步
Table of Contents 1. 线程同步 1.1. 用信号量进行同步 1.2. 用互斥量进行同步 2. 参考资料 线程同步 了解线程信号量的基础知识,对深入理解python的线程会大有帮助. ...
- iOS_02_第一个C语言程序(理解编译、连接、运行)
一.开发工具的选择 1. 可以用来写代码的工具:记事本.ULtraEdit.Vim.Xcode等. 2. 选择XCode的原因:苹果公司官方提供的开发利器.简化开发的工程.有高亮显示功能. 3. 使用 ...
- 深入理解C语言-深入理解数组
数组,作为C语言中常见的复杂数据类型,了解其本质有助于深入了解C语言 数组概念 元素类型角度:数组是相同类型的变量的有序集合测试指针变量占有内存空间大小 内存角度:联系的一大片内存空间 数组初始化 数 ...
- 深入理解C语言-深入理解内存四区
数组与指针 当数组做函数参数的时候,会退化为一个指针 此时在函数内是得不到数组大小的 因此,数组做函数参数的时候需要传递数组大小,也就是多传递一个参数 void func(int arr[], int ...
随机推荐
- 开发APP遇到的问题
1.代码尽量复用 2.调用高德地图,直辖市等,省字段一定有值,市可能为空(pro:'北京市',city:[]) 3.支付密码不用组件 <template> <view> < ...
- getting session bus failed: //bin/dbus-launch terminated abnormally with the following error: Autolaunch error: X11 initialization failed.
今天在调试dbus程序的时候,运行程序出现了getting session bus failed: //bin/dbus-launch terminated abnormally with the f ...
- html的鼠标双击,单击,移上,离开,事件捕捉,JavaScript
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 个人项目 源程序特征统计程序(C++)
零.GitHub地址 https://github.com/King-Authur/Word-count 一.项目的相关要求 wc.exe 是一个常见的工具,它能统计文本文件的字符数.单词数和行数.这 ...
- lammps_data文件
一.notes: 1.不在data文件里写“#”(注释),否则,容易出错: 2.前两行不用写东西(建议): 3.相互作用系数可以不用写在data里边(如pair_coeff等),可有可无,but fo ...
- IDEA下Maven项目搭建踩坑记----2.项目编译之后 在service层运行时找不到 com.dao.CarDao
项目写的差不多 想运行一下,然后发现运行到Service层的时候报错说找不到Dao层文件 ,纠结半天之后看了下编译好的项目文件,发现mapper文件下边是空的, 于是就百度找一下原因,结果说是IDEA ...
- .net core 返回业务错误(不抛异常)
在开始之前你需要知道: 1.通过抛异常--全局捕获异常的方式返回业务错误信息性能是非常差的(不知道为什么的可以百度一下) 2.如何将错误信息绑定到mvc模型验证中 自定义返回内容 //返回内容接口 p ...
- ABP VNext实践之搭建可用于生产的IdentityServer4
一.前言 用了半年多的abp vnext,在开发的效果还是非常的好,可以说节省了很多时间,像事件总线.模块化开发.动态API进行远程调用.自动API控制器等等,一整套的规范,让开发人员更方便的集成,提 ...
- android开发之动画的详解 整理资料 Android开发程序小冰整理
/** * 作者:David Zheng on 2015/11/7 15:38 * * 网站:http://www.93sec.cc * * 微博:http://weibo.com/mcxiaob ...
- Android开发之开源框架OKHTTP的Get请求代码,得到json字符串方法
<span style="white-space:pre"> </span><pre name="code" class=&q ...