你知道golang中Context的使用场景有哪些吗

发布时间:2023-10-10 点击:140
下面由golang教程栏目给大家介绍golang中context的使用场景,希望对需要的朋友有所帮助!
golang中context的使用场景
context在go1.7之后就进入标准库中了。它主要的用处如果用一句话来说,是在于控制goroutine的生命周期。当一个计算任务被goroutine承接了之后,由于某种原因(超时,或者强制退出)我们希望中止这个goroutine的计算任务,那么就用得到这个context了。
本文主要来盘一盘golang中context的一些使用场景:
场景一:rpc调用
在主goroutine上有4个rpc,rpc2/3/4是并行请求的,我们这里希望在rpc2请求失败之后,直接返回错误,并且让rpc3/4停止继续计算。这个时候,就使用的到context。
这个的具体实现如下面的代码。
package mainimport ( "context" "sync" "github.com/pkg/errors")func rpc(ctx context.context, url string) error { result := make(chan int) err := make(chan error) go func() { // 进行rpc调用,并且返回是否成功,成功通过result传递成功信息,错误通过error传递错误信息 issuccess := true if issuccess { result <- 1 } else { err <- errors.new("some error happen") } }() select { case <- ctx.done(): // 其他rpc调用调用失败 return ctx.err() case e := <- err: // 本rpc调用失败,返回错误信息 return e case <- result: // 本rpc调用成功,不返回错误信息 return nil }}func main() { ctx, cancel := context.withcancel(context.background()) // rpc1调用 err := rpc(ctx, "http://rpc_1_url") if err != nil { return } wg := sync.waitgroup{} // rpc2调用 wg.add(1) go func(){ defer wg.done() err := rpc(ctx, "http://rpc_2_url") if err != nil { cancel() } }() // rpc3调用 wg.add(1) go func(){ defer wg.done() err := rpc(ctx, "http://rpc_3_url") if err != nil { cancel() } }() // rpc4调用 wg.add(1) go func(){ defer wg.done() err := rpc(ctx, "http://rpc_4_url") if err != nil { cancel() } }() wg.wait()}当然我这里使用了waitgroup来保证main函数在所有rpc调用完成之后才退出。
在rpc函数中,第一个参数是一个cancelcontext, 这个context形象的说,就是一个传话筒,在创建cancelcontext的时候,返回了一个听声器(ctx)和话筒(cancel函数)。所有的goroutine都拿着这个听声器(ctx),当主goroutine想要告诉所有goroutine要结束的时候,通过cancel函数把结束的信息告诉给所有的goroutine。当然所有的goroutine都需要内置处理这个听声器结束信号的逻辑(ctx->done())。我们可以看rpc函数内部,通过一个select来判断ctx的done和当前的rpc调用哪个先结束。
这个waitgroup和其中一个rpc调用就通知所有rpc的逻辑,其实有一个包已经帮我们做好了。errorgroup。具体这个errorgroup包的使用可以看这个包的test例子。
有人可能会担心我们这里的cancel()会被多次调用,context包的cancel调用是幂等的。可以放心多次调用。
我们这里不妨品一下,这里的rpc函数,实际上我们的这个例子里面是一个“阻塞式”的请求,这个请求如果是使用http.get或者http.post来实现,实际上rpc函数的goroutine结束了,内部的那个实际的http.get却没有结束。所以,需要理解下,这里的函数最好是“非阻塞”的,比如是http.do,然后可以通过某种方式进行中断。比如像这篇文章cancel http.request using context中的这个例子:
func httprequest( ctx context.context, client *http.client, req *http.request, respchan chan []byte, errchan chan error) { req = req.withcontext(ctx) tr := &http.transport{} client.transport = tr go func() { resp, err := client.do(req) if err != nil { errchan <- err } if resp != nil { defer resp.body.close() respdata, err := ioutil.readall(resp.body) if err != nil { errchan <- err } respchan <- respdata } else { errchan <- errors.new("http request failed") } }() for { select { case <-ctx.done(): tr.cancelrequest(req) errchan <- errors.new("http request cancelled") return case <-errchan: tr.cancelrequest(req) return } }}它使用了http.client.do,然后接收到ctx.done的时候,通过调用transport.cancelrequest来进行结束。
我们还可以参考net/dail/dialcontext
换而言之,如果你希望你实现的包是“可中止/可控制”的,那么你在你包实现的函数里面,最好是能接收一个context函数,并且处理了context.done。
场景二:pipeline
pipeline模式就是流水线模型,流水线上的几个工人,有n个产品,一个一个产品进行组装。其实pipeline模型的实现和context并无关系,没有context我们也能用chan实现pipeline模型。但是对于整条流水线的控制,则是需要使用上context的。这篇文章pipeline patterns in go的例子是非常好的说明。这里就大致对这个代码进行下说明。
runsimplepipeline的流水线工人有三个,linelistsource负责将参数一个个分割进行传输,lineparser负责将字符串处理成int64,sink根据具体的值判断这个数据是否可用。他们所有的返回值基本上都有两个chan,一个用于传递数据,一个用于传递错误。(<-chan string, <-chan error)输入基本上也都有两个值,一个是context,用于传声控制的,一个是(in <-chan)输入产品的。
我们可以看到,这三个工人的具体函数里面,都使用switch处理了case <-ctx.done()。这个就是生产线上的命令控制。
func lineparser(ctx context.context, base int, in <-chan string) ( <-chan int64, <-chan error, error) { ... go func() { defer close(out) defer close(errc) for line := range in { n, err := strconv.parseint(line, base, 64) if err != nil { errc <- err return } s

怎么申请网址啊?企业如何注册网址域名呢?
阿里云服务器怎么联系客服
gov.cn域名多少钱
新手须知:让网站文章快速被收录技巧
中文域名中国有何意义,.中国域名的注册需要注意什么?
购买的域名能保留多久?购买的域名可以马上用吗?
武汉域名在哪注册?注册域名有什么注意事项?
想注销刚买的ecs云服务器器