context包优化Go服务代码

什么是context包

关于context包的描述有一篇官方的博客,讲的比较详细,我也推荐过多次,这里结合博客内容再讨论下。 最早的context包出现于golang.org/x/net/context,作为一个候补标准库存在,直到Go1.7版本的发布,context包被添加到标准库中, 同时在Go1.7版本中,标准库中的其它包也对应使用了context包来做一些并发控制的事情,例如net/http。在即将发布的Go1.8版本中,在更多的标准库中引入了context,例如:database/sqlcontext将会Go语言中编写并发模式代码的重要工具。

context

主要类型Context的定义

// A Context carries a deadline, cancelation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.
type Context interface {
    // Done returns a channel that is closed when this Context is canceled
    // or times out.
    Done() <-chan struct{}

    // Err indicates why this context was canceled, after the Done channel
    // is closed.
    Err() error

    // Deadline returns the time when this Context will be canceled, if any.
    Deadline() (deadline time.Time, ok bool)

    // Value returns the value associated with key or nil if none.
    Value(key interface{}) interface{}
}

Done方法返回一个通道,通过这个通道可以监听信号,判断当前的context是否已经关闭。Err方法返回一个错误,描述context为何关闭。Deadline方法返回当前context的deadline以及是否被设置了deadline。Value方法用于获取context中包含的值。

从上面的逻辑可以看出,context不仅可以控制并发逻辑,而且本身也可以携带变量,类似于Map。并且提供Value方法用于获取指定Key的Value值。 上面描述一个context具备的功能,接下来描述下如何构造一个context。Context之间是具有父子关系的,新的Context往往从已有的Context中创建, 因此,最终所有的context组成一颗树状的结构。在context包中提供一个创建初始Context的方法

// Background returns an empty Context. It is never canceled, has no deadline,
// and has no values. Background is typically used in main, init, and tests,
// and as the top-level Context for incoming requests.
func Background() Context

可以认为Backgraund就是所有context树的根。

WithCancelWithTimeout两个方法用于在已有的context上创建新的context,同时从新的context中可以获取到旧的context中保存的Key,Value。通常会在处理完一次请求之后,关闭当前context。

// WithCancel returns a copy of parent whose Done channel is closed as soon as
// parent.Done is closed or cancel is called.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

// A CancelFunc cancels a Context.
type CancelFunc func()

// WithTimeout returns a copy of parent whose Done channel is closed as soon as
// parent.Done is closed, cancel is called, or timeout elapses. The new
// Context's Deadline is the sooner of now+timeout and the parent's deadline, if
// any. If the timer is still running, the cancel function releases its
// resources.
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

WithCancel 方法也通常使用在停掉某些并发调用的逻辑上,WithTimeout 方法用于控制向下游发起请求时的超时。基于这些方法创建的context的关闭只会影响其本身以及后代节点,而不会影响其祖先节点。

最后还有一个设置context中Key,Value的方法

// WithValue returns a copy of parent whose Value method returns val for key.
func WithValue(parent Context, key interface{}, val interface{}) Context

同样也是基于已有的ctx创建出一个新的context,后续可以通过context类型自带的方法Value来获取对应Key的值。

context可以被多个并发的Goroutine使用,对context的访问是并发安全的。

例子

上面简单描述了context的接口,形象的表达,多个具有父子关系的context就是一颗树,这颗树的根节点是Background。除了根context之外,所有的ctx都是基于已有context而创建,下面举一个简单的例子演示context的使用方法。

func Counter(ctx context.Context) int {
    count := 0
    for {
            select {
            case <-ctx.Done():
                    return count
            default:
            }
            count++
            time.Sleep(time.Second)
    }
    return count
}

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    c := make(chan int)
    go func() {
            c <- Counter(ctx)
    }()
    time.Sleep(5 * time.Second)
    cancel()
    fmt.Println(<-c)

}

输出结果: 5

我们修改main函数如下:

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()
    fmt.Println(Counter(ctx))
}

输出结果:5

上面两个是比较简单地使用context控制并发的例子,WithCancel 和 WithTimeout 可以在不同场景下使用。

在实际开发中,context更多的是和网络请求结合使用,例如HTTP请求(Go1.7增加Request的WithContext方法),数据请求(Go1.8会加入Context控制超时),RPC调用请求(kite框架中的client.Call(ctx, ...))。完整的例子可以查看官方博客

posted @ 2017-06-20 20:20  scu_2008_hike  阅读(257)  评论(0编辑  收藏  举报