V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
guonaihong
V2EX  ›  Go 编程语言

golang context 简介(1)

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

    这个系列主要聊下 context 的出发点,带来了哪些便利的地方,常用 API,以及源代码分析

    很多童鞋忽略的问题

    API 服务是很多童鞋开发过的套路,从 API 取得数据或者控制字段,查询数据库返回业务数据。 那好,问题来了,如果 http client(curl 或者浏览器)在数据没有返回的过程中,异常中止了。你的 API 服务是不是还在傻傻的查询呢? 先来个伪代码模拟,下面会用 fmt.Printf+time.Sleep 模拟数据库查询操作。

    package main
    
    import (
        "fmt"
        "github.com/gin-gonic/gin"
        "time"
    )
    
    func main() {
    
        router := gin.Default()
        router.POST("/", func(c *gin.Context) {
    
            //模拟操作数据库
            for i := 0; i < 200; i++ {
                fmt.Printf("read db\n")
                time.Sleep(time.Second * 1)
            }
        })  
    
        router.Run()
    }
    // curl -X POST 127.0.0.1:8080/
    // 你会发现如果客户段 ctrl+c 后,还会不停打印 read db,直到计算器结束。
    
    

    调研 http.Request 数据结构

    上面的资源浪费有没有办法优化?先瞄下 http.Request 源代码。好像有个 context 的东西还挺有意思的。

    type Request struct {
        // ctx is either the client or server context. It should only
        // be modified via copying the whole Request using WithContext.
        // It is unexported to prevent people from using Context wrong
        // and mutating the contexts held by callers of the same request.
        ctx context.Context
    }
    
    // Context returns the request's context. To change the context, use 
    // WithContext.
    //
    // The returned context is always non-nil; it defaults to the
    // background context.
    //
    // For outgoing client requests, the context controls cancelation.
    //
    // For incoming server requests, the context is canceled when the
    // client's connection closes, the request is canceled (with HTTP/2),
    // or when the ServeHTTP method returns.
    func (r *Request) Context() context.Context {
        if r.ctx != nil {
            return r.ctx
        }
        return context.Background()
    }
    
    

    改造资源浪费代码

    package main
    
    import (
        "fmt"
        "github.com/gin-gonic/gin"
        "time"
    )
    
    func main() {
    
        router := gin.Default()
        router.POST("/", func(c *gin.Context) {
    
            //模拟操作数据库
            ctx := c.Request.Context()
            for i := 0; i < 200; i++ {
                select {
                case <-ctx.Done(): // 新增加的关键代码
                    fmt.Printf("found client ctrl+c\n")
                    return
                default:
                    fmt.Printf("read db\n")
                }
                time.Sleep(time.Second * 1)
            }
        })
    
        router.Run()
    }
    
    // curl -X POST 127.0.0.1:8080/
    // 你会发现如果客户段 ctrl+c 后,已经不打印出来 read db。
    

    ok,完美解决资源浪费的问题。 还有更多玩法,下篇介绍。

    我的 github

    https://github.com/guonaihong/gout

    12 条回复    2019-09-24 18:25:08 +08:00
    poplar50
        1
    poplar50  
       2019-09-23 09:46:24 +08:00 via Android
    request with context go1.7 出的功能 是很好用
    guonaihong
        2
    guonaihong  
    OP
       2019-09-23 12:31:45 +08:00
    @poplar50 是的好用,最重要的是 context 已经被官方扶正,后面一些并发套路都要用 context 的设计模式了。
    AngelCriss
        3
    AngelCriss  
       2019-09-23 12:49:58 +08:00 via Android   ❤️ 1
    一个连续的过程该等多久还是得等多久,比如同步查数据库要花 10 分
    AngryPanda
        4
    AngryPanda  
       2019-09-23 12:55:28 +08:00 via Android   ❤️ 1
    楼主的语文需要重修。
    guonaihong
        5
    guonaihong  
    OP
       2019-09-23 12:59:19 +08:00
    @AngryPanda 可有重修的门路?
    guonaihong
        6
    guonaihong  
    OP
       2019-09-23 13:27:01 +08:00
    @AngelCriss 兄弟,没太明白提出的问题。。。
    sealingpp
        7
    sealingpp  
       2019-09-23 14:51:14 +08:00 via iPhone
    赞,昨天因为在自己写接口的时候发现读库的时候有个 query 跟 quertContext 两个方法,于是去查了一下 context 的相关知识,只是有了解还不太会用
    lazyfighter
        8
    lazyfighter  
       2019-09-23 15:58:11 +08:00   ❤️ 1
    其实我一直没有理解 TODO 以及 backGround 两个使用的特点,什么时候用哪个
    labulaka521
        9
    labulaka521  
       2019-09-23 19:28:42 +08:00 via Android
    @lazyfighter todo 就是暂时作为父 context 后续可能会改,另一个就是父 context 不会改了
    guonaihong
        10
    guonaihong  
    OP
       2019-09-23 20:03:13 +08:00
    @lazyfighter
    从实现上来看没啥区别,都是从 emptyCtx 构造出来。更多的区别还是字面意思。
    ```go
    var (
    background = new(emptyCtx)
    todo = new(emptyCtx)
    )

    // Background returns a non-nil, empty Context. It is never canceled, has no
    // values, and has no deadline. It is typically used by the main function,
    // initialization, and tests, and as the top-level Context for incoming
    // requests.
    func Background() Context {
    return background
    }

    // TODO returns a non-nil, empty Context. Code should use context.TODO when
    // it's unclear which Context to use or it is not yet available (because the
    // surrounding function has not yet been extended to accept a Context
    // parameter).
    func TODO() Context {
    return todo
    }
    ```
    AngelCriss
        11
    AngelCriss  
       2019-09-24 18:02:18 +08:00 via Android
    @guonaihong 楼主以前是不是写 PHP 的啊?
    guonaihong
        12
    guonaihong  
    OP
       2019-09-24 18:25:08 +08:00
    @AngelCriss 以前写 linux c 的。怎么了?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5570 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 08:56 · PVG 16:56 · LAX 00:56 · JFK 03:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.