GOLANG的context和并发模型

GOLANG1.7新增了context,最初这个package是在golang.org/x/net/context中的,后来证明对不少程序都是必须的,就归入了标准库。golang

对context的介绍是在context,读这个blog以前,要先读pipelines(pipelines提出了使用close(chan)的方式广播退出事件)。web

通常来讲,context是用在request的处理,例如http请求的处理中,可能会开启多个goroutine,好比:c#

http.HandleFunc(func(w http.ResponseWriter, r *http.Request){
    wg := sync.WaitGroup{}
    wg.Add(2)

    var result0 string
    go func(){
        defer wg.Done()
        res0,err := http.Get("http://server0/api0")
        // Parse res0 to result0.
    }()

    var result1 string
    go func(){
        defer wg.Done()
        res1,err := http.Get("http://server1/api1")
        // Parse res1 to result1.
    }()

    wg.Wait()
    w.Write([]byte(result0 + result1)
})

实际上这个程序是不能这么写的,若是这两个goroutine请求的时间比较长,让用户一直等着么?若是用户取消了请求,关闭了链接呢?若是用户指定了超时时间呢?api

另外,考虑如何关闭一个http服务器,好比须要关闭listener后从新侦听一个新的端口:服务器

var server *http.Server

    go func() {
        server = &http.Server{Addr: addr, Handler: nil}
        if err = server.ListenAndServe(); err != nil {
            ol.E(nil, "API serve failed, err is", err)
            return
        }
    }()

咱们如何确保goroutine已经退出后,而后才返回从新开启服务器?若是只是server.Close(或者GO1.8以前用listener.Close),如何接收外部的事件?若是是goroutine本身的问题,例如端口占用了,如何通知程序退出?直接用os.Exit明显是太粗鲁的作法。ide

context中,有一段话很是关键:svg

A Context does not have a Cancel method for the same reason the 
Done channel is receive-only: the function receiving a 
cancelation signal is usually not the one that sends the signal. 
In particular, when a parent operation starts goroutines for sub-
operations, those sub-operations should not be able to cancel the 
parent. Instead, the WithCancel function (described below) 
provides a way to cancel a new Context value.

也就是说,context没有提供Cancel方法,由于parent goroutine会调用Cancel,在全部sub goroutines中只须要读context.Done()就能够,也就是只是接收退出信号。ui

还有一处地方,说明了Context应该放在参数中:spa

Server frameworks that want to build on Context should provide 
implementations of Context to bridge between their packages and 
those that expect a Context parameter. Their client libraries 
would then accept a Context from the calling code. By 
establishing a common interface for request-scoped data and 
cancelation, Context makes it easier for package developers to 
share code for creating scalable services.

只读取chan的好处是,可使用close(chan)方式通知全部goroutine退出。scala

使用context和WaitGroup,同步和取消服务器的例子:

func HTTPAPIServe(ctx context.Context) {
    http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
    })

    ctx,cancel := context.WithCancel(ctx)

    wg := &sync.WaitGroup{}
    defer wg.Wait()

    wg.Add(1)
    var server *http.Server
    go func(ctx context.Context) {
        defer wg.Done()
        defer cancel()
        server = &http.Server{Addr: addr, Handler: nil}
        _ = server.ListenAndServe()
    }(ctx)

    select {
    case <-ctx.Done():
        server.Close()
    }
}

wg := sync.WaitGroup{}
defer wg.Wait()

ctx, cancel := context.WithCancel(context.Background())

wg.Add(1)
go func(ctx context.Context) {
    defer wg.Done()
    defer cancel()
    HTTPAPIServe(ctx)
}(ctx)

wg.Add(1)
go func(ctx context.Context) {
    defer wg.Done()
    defer cancel()
    // Other server, such as:
    // UDPServer(ctx)
}(ctx)

这个程序实际上包含了几条通道:

  1. 若是须要主动退出,通知全部listener作清理而后退出,能够在parent goroutine调用cancel。上面是在任意goroutine退出后,通知全部goroutine退出。
  2. 若是某个sub-goroutine须要通知其余sub-goroutine退出,不该该直接调用cancel方法,而是经过chan(上面是quit)在goroutine返回时告知parent goroutine,由parent goroutine处理。
  3. 在sub-goroutine中,若是还须要启动goroutine,能够新开context作同步。若是是能够直接用select,那就能够不用新开goroutine,而是直接select ctx,等待退出。通常而言,goroutine的退出,就意味着一个退出的事件,而这个事件应该由parent goroutine处理,而不能直接广播给其余goroutine。
  4. chan的读写,参考读取 chan写入chan

特别是对于library,在参数中支持context,是很是重要的一个要素,这样能够收取到user要求退出或cancel的信号。