使用 Go 语言 context 包的取消功能

使用 Go 语言 context 包的取消功能,第1张

概述via:https://www.sohamkamani.com/golang/2018-06-17-golang-using-context-cancellation/作者:SohamKamani大家好,我是Seekload。有些文章会涉及到很多第三方资源,比如代码地址、与文章内容相关的其他优质阅读源等,但微信公众号文章不支持外部链接,就很尴尬。为了方便大家阅读,以后

via:
https://www.sohamkamani.com/golang/2018-06-17-golang-using-context-cancellation/
作者:Soham Kamani

大家好,我是 Seekload。

有些文章会涉及到很多第三方资源,比如代码地址、与文章内容相关的其他优质阅读源等,但微信公众号文章不支持外部链接,就很尴尬。为了方便大家阅读,以后文章会同步至四哥的博客。

因为之前没怎么宣传,应该很少有人知道四哥的博客地址:

seekload.net

应该很容易记住

 

开始今天的文章,原文如下:


使用过 Go 语言的人对 context 包应该都不陌生。context 包经常用于需要执行一些下游 *** 作的地方,比如:执行 http 请求、从数据库获取数据或者使用协程执行异步 *** 作。最普通的用法就是向下游传递一些公共数据。然而,一个 context 鲜为人知但是非常有用的功能就是可以用于中途取消或者停止 *** 作。

接下来的内容我们将会讨论如何使用 context 提供的取消功能,并提供一些最佳实践供参考,为你编写效率更高、代码更健壮的程序提供借鉴。

为什么我们需要取消?

简而言之,取消是为了防止系统做一些不必要的工作。

我们拿一种常见的场景举例,比如:用户发出 http 请求,从数据获取数据并将数据返回给客户端。

如果一切正常的话,时序图应该是下面这样的:

但是,如果客户端中途取消了请求会发生什么?类似的场景,比如:关闭了浏览器等。如果不取消 *** 作,服务器和数据库仍然会继续完成执行,即使它们的执行成果会被浪费:

理想情况下,如果我们知道请求中断了,我们希望该请求下游的所有工作组件都停止执行。

Context cancellation in Go

现在我们已经知道为什么需要取消,接着就来看看如何实现。因为取消事件与事务和 *** 作高度相关,所以很自然将它与 context 联系在一起。

取消主要有两个方面:

监听取消事件;

发出取消事件;

监听取消事件

Context 类型提供了 Done() 方法,每次 context 接收到取消事件时,该方法都是返回一个 channel,这个 channel 会收到空结构体类型的数据。监听取消事件也很容易,<- ctx.Done()。

比如,一个 http 请求处理需要两秒,如果在中途取消就必须立即返回。

func main() {
    // Create an http server that Listens on port 8000
    http.ListenAndServe(":8000", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := r.Context()
        // This prints to STDOUT to show that processing has started
        fmt.Fprint(os.Stdout, "processing request\n")
        // We use `select` to execute a peice of code depending on which
        // channel receives a message first
        select {
        case <-time.After(2 * time.Second):
            // If we receive a message after 2 seconds
            // that means the request has been processed
            // We then write this as the response
            w.Write([]byte("request processed"))
        case <-ctx.Done():
            // If the request gets cancelled, log it
            // to STDERR
            fmt.Fprint(os.Stderr, "request cancelled\n")
        }
    }))
}

你可以使用 go run 将服务跑起来,在浏览器中打开 localhost:8000,如果你在 2s 钟之内关闭浏览器,终端将会输出 request cancelled。

发出取消事件

如果你有需要取消的 *** 作,可以通过 context 发出取消事件。可以通过 context 包提供的 WithCancel 函数完成,该函数返回 context 对象和一个取消函数,这个函数不带任何参数、没有返回值,当你需要取消 context 时可以调用该函数。

假设有两个相互依赖的 *** 作,这里“依赖”的意思是,如果其中一个 *** 作失败了,另一条 *** 作即使完成也没有任何意义。这个场景里,如果我们事先知道其中一个 *** 作失败了,我们需要取消所有的 *** 作。

func operation1(ctx context.Context) error {
    // Let's assume that this operation Failed for some reason
    // We use time.Sleep to simulate a resource intensive operation
    time.Sleep(100 * time.Millisecond)
    return errors.New("Failed")
}

func operation2(ctx context.Context) {
    // We use a similar pattern to the http server
    // that we saw in the earlIEr example
    select {
    case <-time.After(500 * time.Millisecond):
        fmt.Println("done")
    case <-ctx.Done():
        fmt.Println("halted operation2")
    }
}

func main() {
    // Create a new context
    ctx := context.Background()
    // Create a new context, with its cancellation function
    // from the original context
    ctx, cancel := context.WithCancel(ctx)

    // Run two operations: one in a different go routine
    go func() {
        err := operation1(ctx)
        // If this operation returns an error
        // cancel all operations using this context
        if err != nil {
            cancel()
        }
    }()

    // Run operation2 with the same context we use for operation1
    operation2(ctx)
}
基于时间的取消事件

任何应用程序都需要在超时时间之内维护 SLA 可用性,可以采用基于时间的取消事件。相关的 API 与上面提到的例子类似,但是有一点补充:

// The context will be cancelled after 3 seconds
// If it needs to be cancelled earlIEr, the `cancel` function can
// be used, like before
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)

// The context will be cancelled on 2009-11-10 23:00:00
ctx, cancel := context.WithDeadline(ctx, time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC))

例如,使用 http API 调用外部服务,如果请求时间太长,最好尽早取消请求。

func main() {
    // Create a new context
    // With a deadline of 100 milliseconds
    ctx := context.Background()
    ctx, _ = context.WithTimeout(ctx, 100*time.Millisecond)

    // Make a request, that will call the Google homepage
    req, _ := http.NewRequest(http.MethodGet, "http://Google.com", nil)
    // Associate the cancellable context we just created to the request
    req = req.WithContext(ctx)

    // Create a new http clIEnt and execute the request
    clIEnt := &http.ClIEnt{}
    res, err := clIEnt.Do(req)
    // If the request Failed, log to STDOUT
    if err != nil {
        fmt.Println("Request Failed:", err)
        return
    }
    // Print the statuscode if the request succeeds
    fmt.Println("Response received, status code:", res.StatusCode)
}

输出的结果取决于请求谷歌主页的快慢,有可能输出:

Response received, status code: 200

或者

Request Failed: Get http://Google.com: context deadline exceeded
陷阱和警告

尽管 Context 的取消功能是一个很好用的工具,但是使用时有一些需要主要的点。最重要的是,context 只能被取消一次。如果你希望在同一 *** 作中传递多个错误,那么使用 context 取消可能不是最佳选择。使用取消最常见的场景是仅仅希望取消 *** 作,而不是返回下游 *** 作出现的错误。

需要注意的另一点就是,应将相同的 context  对象传递给可能要取消的所有函数或者协程,使用 WithTimeout 或 WithCancel 包装一个已经可取消的 context 将导致多种可能的上下文被取消,应该避免。

 

ps: 文章完整代码地址

https://github.com/sohamkamani/blog-example-go-context-cancellation

 


这是持续翻译的第 18/100 篇优质文章。
如果你有想交流的话题,欢迎留言。


如果我的文章对你有所帮助,点赞、转发都是一种支持!

总结

以上是内存溢出为你收集整理的使用 Go 语言 context 包的取消功能全部内容,希望文章能够帮你解决使用 Go 语言 context 包的取消功能所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1211899.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-04
下一篇 2022-06-04

发表评论

登录后才能评论

评论列表(0条)

保存