最近線上看到服務(wù)占用內(nèi)存很高,通過pprof查看如圖
簡(jiǎn)化代碼
package main
import (
"context"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"syscall"
"time"
)
func dbQuery(ctx context.Context) {
ctx, cancel := context.WithCancel(ctx)
timer := time.AfterFunc(time.Second*1, cancel)
defer timer.Stop()
// db.QueryContext(ctx,"...")
}
func loop(ctx context.Context) {
ticker := time.NewTicker(time.Microsecond)
for range ticker.C {
dbQuery(ctx)
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go loop(ctx)
go func() {
http.ListenAndServe(":9009", nil)
}()
ch := make(chan os.Signal)
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
<-ch
cancel()
}
dbQuery的父context需要2個(gè)條件,才能導(dǎo)致此問題
1:必須長(zhǎng)期存在
2:Done()返回的chan非nil
具體源碼在context.go中
func propagateCancel(parent Context, child canceler) {
if parent.Done() == nil {
return // parent is never canceled
}
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
// parent has already been canceled
child.cancel(false, p.err)
} else {
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
2個(gè)解決辦法
1確保子context cancel
2 自己實(shí)現(xiàn)一個(gè)實(shí)現(xiàn)context.Context接口的對(duì)象,Done()返回nil