上篇说到了,http 服务可以检测客户端异常终止的事件。通过 select 监听 context.Done(),可以终止不必要的数据库查询,节约资源。 这次聊下,何时使用 context.WithCancel ?
下面的代码,是个 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 会影响他的后代,但是后代挂了不影响父辈。
这里,希望 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() }
1 richzhu 2019-09-24 10:24:34 +08:00 ![]() 没看懂。。。context.WithCancel 的作用是什么,为什么要使用 context.WithCancel |
2 tcp 2019-09-24 10:33:56 +08:00 via Android 忽然被 @了 |
![]() | 3 guonaihong OP @richzhu context.WithCancel 的作用,生成一个新的 context 变量。何时需要使用 WithCancel,要父子影响的时候用 |
![]() | 4 BruceAuyeung 2019-09-24 15:23:44 +08:00 ![]() @guonaihong 你惊奇的发现,只有第 2 个(index 为 1 )以及他的后代退出。我们随意修改 failId 都是这个结论。父 context 会影响他的后代,但是后代挂了不影响父辈。 --- 这个说法错了吧,如果 failId 为 0,所有 context 都可以 done |
![]() | 5 guonaihong OP 我少描述一句,原话想说的是: testContext(1, 3) 时。你惊奇的发现,只有第 2 个(index 为 1 )以及他的后代退出。我们随意修改 failId 都是这个结论,父 context 会影响他的后代,但是后代挂了不影响父辈。 感谢指出,我把有歧义的地方修改了。 |
![]() | 6 BruceAuyeung 2019-09-24 15:43:54 +08:00 @guonaihong 何时需要使用 WithCancel,要父子影响的时候用 --- 这个结论也欠妥。WithCancel 适用于所有希望可以手动取消的耗时任务。不过本文指出了一个很有意思的特性,子(包括孙等) context 会随父 context 的取消而取消。 期待更多好文,加油! |
![]() | 7 guonaihong OP @BruceAuyeung 感谢指出,明天还有一篇,欢迎 review |
![]() | 8 BruceAuyeung 2019-09-24 17:49:16 +08:00 via Android @guonaihong 不敢 |