go context踩坑记

最近线上看到服务占用内存很高,通过pprof查看如图


image.png

简化代码

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个条件,才能导致此问题
1:必须长期存在
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个解决办法
1确保子context cancel
2 自己实现一个实现context.Context接口的对象,Done()返回nil

你可能感兴趣的:(go context踩坑记)