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

原创
2019/09/27 09:22
阅读数 739

上篇说到了,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)
    }   
}

testContext(1, 3) 时。你惊奇的发现,只有第 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

展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部