Last active
November 18, 2019 10:38
-
-
Save haozibi/801edeb1434906d5a05a99d1b1a47973 to your computer and use it in GitHub Desktop.
多个并发请求只让其中一个真正执行,其余阻塞等待到执行的那个请求完成,将结果传递给阻塞的其他请求达到防止雪崩的效果
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
// by haozibi | |
// https://play.golang.org/p/QiQ4sBoVbCu | |
// | |
// 参考: | |
// https://segmentfault.com/a/1190000018464029 | |
// https://github.com/golang/sync/tree/master/singleflight | |
// https://github.com/golang/groupcache/tree/master/singleflight | |
import ( | |
"fmt" | |
"sync" | |
"sync/atomic" | |
"time" | |
"github.com/golang/groupcache/singleflight" | |
) | |
func main() { | |
err := single() | |
if err != nil { | |
panic(err) | |
} | |
} | |
// "github.com/golang/groupcache/singleflight" 源码相对简单,结构清晰 | |
// "golang.org/x/sync/singleflight" 比较完善,原理和👆的一致 | |
func single() error { | |
var g singleflight.Group | |
c := make(chan string) | |
t1 := time.Now() | |
var calls int32 | |
fn := func() (interface{}, error) { | |
// calls 统计 fn 运行次数,原子操作 | |
atomic.AddInt32(&calls, 1) | |
// 阻塞,直到 chan c 接收到值 | |
return <-c, nil | |
} | |
const n = 10 | |
var wg sync.WaitGroup | |
for i := 0; i < n; i++ { | |
wg.Add(1) | |
// 小技巧 | |
// 如果不设置 i:=i,则多个协程读取的都是同一个 i,也是唯一的 i,即 i:=0;i<n;i++ 中的 i | |
// 也可以通过传送 go func(i int){} 的方式解决此问题 | |
i := i | |
// 启动多个协程同时执行 | |
go func() { | |
v, err := g.Do("key", fn) | |
if err != nil { | |
fmt.Printf("Do error: %v", err) | |
return | |
} | |
// if v.(string) != "bar" { | |
// fmt.Printf("got %q; want %q", v, "bar") | |
// return | |
// } | |
fmt.Println(i, v) | |
wg.Done() | |
}() | |
} | |
// sleep 100ms 有两个作用 | |
// 1. 等待,让多个协程都启动成功,处于等待第一个协程完成的状态 | |
// 2. 第一个启动成功的协程会 “真正” 执行 fn, 100ms 也相当于 fn 执行耗时,第一个协程需要等 100ms 才能接收到 chan c 的输入 | |
// ps: 在 "github.com/golang/groupcache/singleflight" 包中的 Group | |
// 当 “真正执行” fn 的协程完成了会立即从 map 中删除标致位(对应的 key) | |
// 所以只有在 “真正执行” fn 执行的过程中(此例为 100ms)进入到 Group 的协程才能获取到共同的返回值 | |
// ps: 等第一个 fn 执行完毕后缓存就可以查到,下一次再执行到这个方法的场景只有缓存失效之后了 | |
time.Sleep(100 * time.Millisecond) // let goroutines above block | |
c <- "bar" | |
// 等待所有协程完成 | |
wg.Wait() | |
// 检查 fn 的运行次数 | |
got := atomic.LoadInt32(&calls) | |
fmt.Printf("calls: %d,time: %v\n", got, time.Since(t1)) | |
return nil | |
} | |
// output: | |
// 4 bar | |
// 1 bar | |
// 8 bar | |
// 3 bar | |
// 2 bar | |
// 0 bar | |
// 7 bar | |
// 9 bar | |
// 6 bar | |
// 5 bar | |
// calls: 1,time: 100.576411ms |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
http 中间件实现请求的 http.Handler
go test