V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
guonaihong
V2EX  ›  程序员

golang context 简介(2)-何时使用 WithCancel

  •  
  •   guonaihong ·
    guonaihong · 2019-09-24 09:24:48 +08:00 · 2920 次点击
    这是一个创建于 1890 天前的主题,其中的信息可能已经有所发展或是发生改变。

    上篇说到了,http 服务可以检测客户端异常终止的事件。通过 select 监听 context.Done(),可以终止不必要的数据库查询,节约资源。 这次聊下,何时使用 context.WithCancel ?

    父子 context 的影响

    下面的代码,是个 context 调用链。父-->子-->子子-->子子子-->子子子子结构。通过不停的派生新的 context 生成后代。我们可以调整 failId 控制父还是子子提前退出。

    比如 testContext(1, 3) 生成 3 个 context,第 2 个退出。

    func testContext(failId int, max int) {
        ctxs := make([]context.Context, 0, max)
        cancels := make([]func(), 0, max)
    
        var (
            ctx    context.Context
            cancel func()
            wg     sync.WaitGroup
        )   
    
        for i := 0; i < max; i++ {
            if i == 0 { 
                ctx, cancel = context.WithCancel(context.Background())
            } else {
                ctx, cancel = context.WithCancel(ctxs[i-1])
            }
    
            ctxs = append(ctxs, ctx)
            cancels = append(cancels, cancel)
        }   
    
        wg.Add(max)
        defer wg.Wait()
    
        for i := 0; i < max; i++ {
    
            go func(id int) {
                defer wg.Done()
    
                if id == failId {
                    cancels[id]()
                }
                select {
                case <-ctxs[id].Done():
                }
    
            }(i)
        }   
    }
    
    

    你惊奇的发现,只有第 2 个(index 为 1 )以及他的后代退出。我们随意修改 failId 都是这个结论。父 context 会影响他的后代,但是后代挂了不影响父辈。

    改造上篇介绍的 API+数据库查询

    这里,希望 http.context 的事件影响到数据库里面,但不希望数据库里面通过黑科技把事件影响到 http。就派生一个新的。 在 gin.Context。c.Request 是*http.Request 对象,改对象有个 Context()方法返回 context,传递给 db.QueryContext 函数

    package main
    
    import (
    	"context"
    	"database/sql"
    	"github.com/gin-gonic/gin"
    	_ "github.com/go-sql-driver/mysql"
    	"log"
    )
    
    func main() {
    	db, err := sql.Open("mysql", "root:123456@tcp(192.168.5.17)/test")
    	if err != nil {
    		log.Printf("err:%s\n", err)
    		return
    	}
    	defer db.Close()
    
    	r := gin.Default()
    
    	r.POST("/test", func(c *gin.Context) {
            // 从 http.Client 派生一个新的 context
    		ctx, cancel := context.WithCancel(c.Request.Context())
    		defer cancel()
    
    		rows, err := db.QueryContext(ctx, "select * from test")
    		if err != nil {
    			c.JSON(200, gin.H{"errcode": 0xff, "errmsg": err.Error()})
    			return
    		}
    
    		names := make([]string, 0, 3)
    		for rows.Next() {
    			var name string
    			rows.Scan(&name)
    			if err != nil {
    				break
    			}
    			names = append(names, name)
    		}
    
    		if closeErr := rows.Close(); closeErr != nil {
    			c.JSON(200, gin.H{"errcode": 0xff, "errmsg": closeErr.Error()})
    			return
    		}
    
    		c.JSON(200, gin.H{"errcode": 0, "errmsg": "ok", "names": names})
    	})
    
    	r.Run()
    }
    

    我的 github

    https://github.com/guonaihong/gout

    8 条回复    2019-09-24 17:49:16 +08:00
    richzhu
        1
    richzhu  
       2019-09-24 10:24:34 +08:00   ❤️ 1
    没看懂。。。context.WithCancel 的作用是什么,为什么要使用 context.WithCancel
    tcp
        2
    tcp  
       2019-09-24 10:33:56 +08:00 via Android
    忽然被 @了😄
    guonaihong
        3
    guonaihong  
    OP
       2019-09-24 10:34:31 +08:00
    @richzhu context.WithCancel 的作用,生成一个新的 context 变量。何时需要使用 WithCancel,要父子影响的时候用
    BruceAuyeung
        4
    BruceAuyeung  
       2019-09-24 15:23:44 +08:00   ❤️ 1
    @guonaihong
    你惊奇的发现,只有第 2 个(index 为 1 )以及他的后代退出。我们随意修改 failId 都是这个结论。父 context 会影响他的后代,但是后代挂了不影响父辈。 --- 这个说法错了吧,如果 failId 为 0,所有 context 都可以 done
    guonaihong
        5
    guonaihong  
    OP
       2019-09-24 15:27:57 +08:00
    我少描述一句,原话想说的是:
    testContext(1, 3) 时。你惊奇的发现,只有第 2 个(index 为 1 )以及他的后代退出。我们随意修改 failId 都是这个结论,父 context 会影响他的后代,但是后代挂了不影响父辈。

    感谢指出,我把有歧义的地方修改了。
    BruceAuyeung
        6
    BruceAuyeung  
       2019-09-24 15:43:54 +08:00
    @guonaihong 何时需要使用 WithCancel,要父子影响的时候用 --- 这个结论也欠妥。WithCancel 适用于所有希望可以手动取消的耗时任务。不过本文指出了一个很有意思的特性,子(包括孙等) context 会随父 context 的取消而取消。

    期待更多好文,加油!
    guonaihong
        7
    guonaihong  
    OP
       2019-09-24 16:18:49 +08:00
    @BruceAuyeung 感谢指出,明天还有一篇,欢迎 review
    BruceAuyeung
        8
    BruceAuyeung  
       2019-09-24 17:49:16 +08:00 via Android
    @guonaihong 不敢
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1643 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 16:56 · PVG 00:56 · LAX 08:56 · JFK 11:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.