专栏名称: 唤之
目录
相关文章推荐
OSC开源社区  ·  谈谈DeepSeek-R1满血版推理部署和优化 ·  2 天前  
51CTO官微  ·  DeepSeek爆火!我们整理了80余页宝典 ... ·  3 天前  
OSC开源社区  ·  AI的三岔路口:专业模型和个人模型 ·  4 天前  
OSC开源社区  ·  DS豆包通义BTY王炸组合,我做了个元宵AI ... ·  3 天前  
程序员的那些事  ·  马斯克开团豪掷 974 ... ·  4 天前  
51好读  ›  专栏  ›  唤之

上下文中断在Go语言中的使用

唤之  · 掘金  · 程序员  · 2018-07-02 09:57

正文

越来越多的小伙伴开始使用Go语言开发应用,大多数情况会使用上下文操作,如进行HTTP调用,或从数据库中获取数据,或与go-routines执行异步操作。 最常见的用途是传递可供所有下游操作使用的常用数据。

为什么我们需要中断操作?

通常情况下我们我们的应该通过HTTP 发起请求,然后查询数据库再通过HTTP请求将数据返回

如果一切都完美的话时序图应该像下面这样:

但是,如果用户在中间取消请求会发生什么? 例如,如果客户端中间请求关闭浏览器,就可能发生这种情况。 如果没有取消,应用服务器和数据库将继续工作资源会被浪费:

理想情况下,如果我们知道进程(在本例中为HTTP请求)停止,我们希望进程的所有下游组件都需要停止:

Go语言中的上下文中断:

现在我们知道为什么需要中断,让我们看看如何在Go中实现它。

我们通常需要实现两种方式的中断:

  1. 监听中断
  2. 发出中断

首先我们看看Context

  1. // context
  2. type Context interface {
  3. Done() <-chan struct{}
  4. Err() error
  5. Deadline() (deadline time.Time, ok bool )
  6. Value(key interface{}) interface {}
  7. }

context.Context 接口

  • context包里的方法是线程安全的,可以被多个线程使用
  • 当Context被canceled或是timeout, Done返回一个被closed 的channel
  • 在Done的channel被closed后, Err代表被关闭的原因
  • 如果存在, Deadline 返回Context将要关闭的时间
  • 如果存在,Value 返回与 key 相关了的值,不存在返回 nil

监听中断:

让我们实现一个两秒钟超时HTTP服务。 如果请求在此之前被取消,我们立即返回结果:

  1. func main() {
  2. // Create an HTTP server that listens on port 8000
  3. http.ListenAndServe(":8000", http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) {
  4. ctx := r.Context()
  5. // This prints to STDOUT to show that processing has started
  6. fmt.Fprint(os.Stdout, "processing request\n")
  7. // We use `select` to execute a peice of code depending on which
  8. // channel receives a message first
  9. select {
  10. case <-time.After(2 * time.Second):
  11. // If we receive a message after 2 seconds
  12. // that means the request has been processed
  13. // We then write this as the response
  14. w.Write([]byte( "request processed"))
  15. case <-ctx.Done():
  16. // If the request gets cancelled, log it
  17. // to STDERR
  18. fmt.Fprint(os.Stderr, "request cancelled\n")
  19. }
  20. }))
  21. }

运行上面代码,如果你在两秒钟以内关闭浏览器你会看的“request cancelled”

发出中断

如果你需要取消操作,则必须通过上下文发出中断操作。 我们可以使用上下文包中的WithCancel函数完成,假设我们一次请求有2个依赖操作。 在这里,“依赖”意味着如果一个人失败了,那么另一个完成就没有意义了。 在这种情况下,如果我们如果道其中一个操作失败了,我们就中断所有操作。

  1. func operation1(ctx context.Context) error {
  2. // Let's assume that this operation failed for some reason
  3. // We use time.Sleep to simulate a resource intensive operation
  4. time.Sleep(100 * time.Millisecond)
  5. return errors.New("failed")
  6. }
  7. func operation2(ctx context.Context) {
  8. // We use a similar pattern to the HTTP server
  9. // that we saw in the earlier example
  10. select {
  11. case <-time.After(500 * time.Millisecond):
  12. fmt.Println("done")
  13. case <-ctx.Done():
  14. fmt.Println("halted operation2")
  15. }
  16. }
  17. func main() {
  18. // Create a new context
  19. ctx := context.Background()
  20. // Create a new context, with its cancellation function
  21. // from the original context
  22. ctx, cancel := context.WithCancel(ctx)
  23. // Run two operations: one in a different go routine
  24. go func() {
  25. err := operation1(ctx)
  26. // If this operation returns an error
  27. // cancel all operations using this context
  28. if err != nil {
  29. cancel()
  30. }
  31. }()
  32. // Run operation2 with the same context we use for operation1
  33. operation2(ctx)
  34. }

基于时间的中断

很多情况下,我们的服务要满足SLA的要求。我们可能需要基于超时机制进行中断!当然我们还可以在RPC请求与DB操作中使用!

  1. // The context will be cancelled after 3 seconds
  2. // If it needs to be cancelled earlier, the `cancel` function can
  3. // be used, like before
  4. ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
  5. // The context will be cancelled on 2009-11-10 23:00:00
  6. ctx, cancel := context.WithDeadline(ctx, time.Date(2009 , time.November, 10, 23, 0, 0 , 0, time.UTC))

例如,对外部服务进行HTTP API调用。 如果请求时间过长,最好提前失败并取消请求:

  1. func main() {
  2. // Create a new context
  3. // With a deadline of 100 milliseconds
  4. ctx := context.Background()
  5. ctx, _ = context.WithTimeout(ctx, 100*time.Millisecond )
  6. // Make a request, that will call the google homepage
  7. req, _ := http.NewRequest(http.MethodGet, "http://google.com", nil )
  8. // Associate the cancellable context we just created to the request
  9. req = req.WithContext(ctx)
  10. // Create a new HTTP client and execute the request
  11. client := &http.Client{}
  12. res, err := client.Do(req)
  13. // If the request failed, log to STDOUT
  14. if err != nil {
  15. fmt.Println("Request failed:", err)
  16. return
  17. }
  18. // Print the statuscode if the request succeeds
  19. fmt.Println("Response received, status code:", res.StatusCode)
  20. }







请到「今天看啥」查看全文