golang Context for goroutines

  • 概要
  • goroutine 的控制
    • 取消控制
    • 超时控制
    • goroutine 之间的传值
  • 总结

概要

golang 的提供的 channel 机制是基于 CSP(Communicating Sequencial Processes)模型的并发模式.

通过 channel, 可以很方便的写出多个 协程 (goroutine)之间协作的代码, 将顺序的代码改成并行的代码非常简单. 改造成并行的代码之后, 虽然可以更好的利用多核的硬件, 有效的提高代码的执行效率, 但是, 也带来了代码控制的问题.

并行的代码显然比顺序执行的代码更难于管理和控制, 这时, 就得靠 golang 提供的 Context 接口来帮助我们控制 goroutine 了.

goroutine 的控制

goroutine 之间最基本的控制, 就是通过 channel 来交互数据:

 1  func routineSample() {
 2    ch := make(chan int, 10)
 3    go p1(ch)
 4    go c1(ch)
 5    go c2(ch)
 6
 7    time.Sleep(10 * time.Second)
 8  }
 9
10  func p1(ch chan int) {
11    fmt.Println("Parent go routine!")
12
13    for i := 0; i < 10; i++ {
14      ch <- i
15    }
16
17    close(ch)
18  }
19
20  func c1(ch chan int) {
21    fmt.Println("Child 1 go routine!")
22    for c := range ch {
23      fmt.Printf("child 1, recivie: %d\n", c)
24      time.Sleep(100 * time.Millisecond)
25    }
26  }
27
28  func c2(ch chan int) {
29    fmt.Println("Child 2 go routine!")
30    for c := range ch {
31      fmt.Printf("child 2, recivie: %d\n", c)
32      time.Sleep(100 * time.Millisecond)
33    }
34  }

上述是最基本的示例, p1 函数不断向 channel 中发送数据, c1 和 c2 负责处理数据. 虽然通过 channel 实现 c1, c2 的并发很简单, 但是可以看出, p1 要想控制 c1, c2 没有那么容易.

这时, 就需要通过 Context 接口来控制并发的协程.

取消控制

取消控制指的是任务的发起者, 在特定条件下, 发送信号指示已经接受到任务的协程停止任务的执行.

 1  func routineSample() {
 2   ch := make(chan int, 10)
 3   ctx, cancel := context.WithCancel(context.Background())
 4   go p1(ctx, ch)
 5   go c1(ctx, ch)
 6   go c2(ctx, ch)
 7
 8   // 300 ms之后取消任务
 9   time.Sleep(300 * time.Millisecond)
10   cancel()
11
12   time.Sleep(10 * time.Second)
13  }
14
15  func p1(ctx context.Context, ch chan int) {
16   fmt.Println("Parent go routine!")
17
18   for i := 0; i < 10; i++ {
19     ch <- i
20   }
21
22   close(ch)
23  }
24
25  func c1(ctx context.Context, ch chan int) {
26   fmt.Println("Child 1 go routine!")
27   for c := range ch {
28     select {
29     case <-ctx.Done():
30       fmt.Println("child 1, return!")
31       return
32     default:
33       fmt.Printf("child 1, recivie: %d\n", c)
34     }
35     time.Sleep(100 * time.Millisecond)
36   }
37  }
38
39  func c2(ctx context.Context, ch chan int) {
40   fmt.Println("Child 2 go routine!")
41   for c := range ch {
42     select {
43     case <-ctx.Done():
44       fmt.Println("child 2, return!")
45       return
46     default:
47       fmt.Printf("child 2, recivie: %d\n", c)
48     }
49     time.Sleep(100 * time.Millisecond)
50   }
51  }

300 毫秒后, 发送取消任务的信号 cancel() , c1 和 c2 通过 select 判断是否有取消信号, 收到取消信号后, 退出处理.

通过执行结果可以看出, c1 和 c2 大约处理 5~6 个任务之后停止退出.

超时控制

除了取消控制, context 还有超时的控制.

 1  func routineSample() {
 2   ch := make(chan int, 10)
 3   ctx, _ := context.WithTimeout(context.Background(), 300*time.Millisecond)
 4   go p1(ctx, ch)
 5   go c1(ctx, ch)
 6   go c2(ctx, ch)
 7
 8   time.Sleep(10 * time.Second)
 9  }
10
11  func p1(ctx context.Context, ch chan int) {
12   fmt.Println("Parent go routine!")
13
14   for i := 0; i < 10; i++ {
15     ch <- i
16   }
17
18   close(ch)
19  }
20
21  func c1(ctx context.Context, ch chan int) {
22   fmt.Println("Child 1 go routine!")
23   for c := range ch {
24     select {
25     case <-ctx.Done():
26       fmt.Println("child 1, return!")
27       return
28     default:
29       fmt.Printf("child 1, recivie: %d\n", c)
30     }
31     time.Sleep(100 * time.Millisecond)
32   }
33  }
34
35  func c2(ctx context.Context, ch chan int) {
36   fmt.Println("Child 2 go routine!")
37   for c := range ch {
38     select {
39     case <-ctx.Done():
40       fmt.Println("child 2, return!")
41       return
42     default:
43       fmt.Printf("child 2, recivie: %d\n", c)
44     }
45     time.Sleep(100 * time.Millisecond)
46   }
47  }

控制超时的 WithTimeout 也返回一个 cancel 函数, 可以在超时到达之前来取消任务的执行, 上面的例子等待超时时间达到后自动取消任务, 没有使用 cancel 函数.

goroutine 之间的传值

一般来说, goroutine 之间通过 channel 传递都是业务数据, 除此之外, 还可以通过 channel 来传递一些控制 goroutine 的元数据.

 1  func routineSample() {
 2   ch := make(chan int, 10)
 3   // 哪个goroutine收到5号任务, 就退出, 不再做后续的任务
 4   ctx := context.WithValue(context.Background(), "finish", 5)
 5   go p1(ctx, ch)
 6   go c1(ctx, ch)
 7   go c2(ctx, ch)
 8
 9   time.Sleep(10 * time.Second)
10  }
11
12  func p1(ctx context.Context, ch chan int) {
13   fmt.Println("Parent go routine!")
14
15   for i := 0; i < 10; i++ {
16     ch <- i
17   }
18
19   close(ch)
20  }
21
22  func c1(ctx context.Context, ch chan int) {
23   fmt.Println("Child 1 go routine!")
24   for c := range ch {
25     if c == ctx.Value("finish").(int) {
26       fmt.Println("child 1, return!")
27       return
28     }
29     fmt.Printf("child 1, recivie: %d\n", c)
30     time.Sleep(100 * time.Millisecond)
31   }
32  }
33
34  func c2(ctx context.Context, ch chan int) {
35   fmt.Println("Child 2 go routine!")
36   for c := range ch {
37     if c == ctx.Value("finish").(int) {
38       fmt.Println("child 2, return!")
39       return
40     }
41     fmt.Printf("child 2, recivie: %d\n", c)
42     time.Sleep(100 * time.Millisecond)
43   }
44  }

上面的例子是在 context 中放置一个 key="finish" 的任务号, 如果 c1 或者 c2 收到的任务号和它相同, 则退出任务的执行. 通过运行上面的例子可以看出, c1 或者 c2 执行到 5 号任务的时候就会退出协程. 但是谁收到 5 号任务是不确定的, 多执行几次上面的代码, 可以发现有时是 c1 退出, 有时是 c2 退出.

总结

context 是控制并发协程的上下文, 利用 context, 可以大量简化控制协程的超时, 取消协程执行, 以及协程之间传值的代码. context 很方便, 但也不能乱用, 通过 channel 传递的业务数据, 不要放在 context 中来传递.

此外, context 是线程安全的, 可以放心的在多个协程中使用.

原文地址:https://www.cnblogs.com/wang_yb/p/12234475.html

时间: 2024-10-16 07:25:13

golang Context for goroutines的相关文章

Golang Context 详细介绍

Golang context 本文包含对context实现上的分析和使用方式,分析部分源码讲解比价多,可能会比较枯燥,读者可以直接跳过去阅读使用部分. ps: 作者本着开源分享的精神撰写本篇文章,如果出现任何误差务必留言指正,作者会在第一时间内修正,共同维护一个好的开源生态,谢谢!!! 一.简介 作者所讲的context的包名称是: "golang.org/x/net/context" ,希望读者不要引用错误了. 在godoc中对context的介绍如下: Package contex

Golang Context 包详解

Golang Context 包详解 0. 引言 在 Go 语言编写的服务器程序中,服务器通常要为每个 HTTP 请求创建一个 goroutine 以并发地处理业务.同时,这个 goroutine 也可能会创建更多的 goroutine 来访问数据库或者 RPC 服务. 当这个请求超时或者被终止的时候,需要优雅地退出所有衍生的 goroutine,并释放资源.因此,我们需要一种机制来通知衍生 goroutine 请求已被取消. 比如以下例子,sleepRandom_1 的结束就无法通知到 sle

golang context

1. 内部结构之 - timerCtx . type timerCtx struct { cancelCtx timer *time.Timer // Under cancelCtx.mu. deadline time.Time } - 里面有一个 timer,用来触发超时之后的 回调函数,也就是超时之后,帮你 cancel 一下.理论上,你不用在结构体里存一份这个,这里存了这个指针,主要是用来取消这个定时触发,基本上就是因为一个定时器你如果不需要了,就要马上回收这个资源,否则会很耗资源的.例如

grpc 入门(二)-- context

本节是继上一章节Hello world的进一步深入挖掘; 一.grpc 服务接口类型 在godoc的网站上对grpc的端口类型进行了简单的介绍,总共有下面4种类型[1]: gRPC lets you define four kinds of service method: Unary RPCs where the client sends a single request to the server and gets a single response back, just like a nor

[开源] gnet: 一个轻量级且高性能的 Golang 网络库

Github 主页 https://github.com/panjf2000/gnet 欢迎大家围观~~,目前还在持续更新,感兴趣的话可以 star 一下暗中观察哦. 简介 gnet 是一个基于 Event-Loop 事件驱动的高性能和轻量级网络库.这个库直接使用 epoll 和 kqueue 系统调用而非标准 Golang 网络包:net 来构建网络应用,它的工作原理类似于两个开源的网络库:libuv 和 libevent. 这个项目存在的价值是提供一个在网络包处理方面能和 Redis.Hap

go context学习

context学习 context的struct设计 type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(key interface{}) interface{} } struct中字段分析 Deadline()返回设置的截止时间和有无设置截止时间 Done()返回一个只读的chan,用来通知是否要释放资源 Err()返回取消的原因 V

[转Go-简洁的并发 ]

http://www.yankay.com/go-clear-concurreny/ Posted on 2012-11-28by yankay 多核处理器越来越普及.有没有一种简单的办法,能够让我们写的软件释放多核的威力?是有的.随着Golang, Erlang, Scala等为并发设计的程序语言的兴起,新的并发模式逐渐清晰.正如过程式编程和面向对象一样,一个好的编程模式有一个极其简洁的内核,还有在此之上丰富的外延.可以解决现实世界中各种各样的问题.本文以GO语言为例,解释其中内核.外延. 并

Cheatsheet: 2017 05.01 ~05.31

Web Configuring Your .npmrc for an Optimal Node.js Environment Web Developer Security Checklist HTTPS on Stack Overflow: The End of a Long Road TypeScript 2.2: Mixin Classes Introducing the TypeScript Cookbook Performance Analysis Reference Golang Us

Golang-简洁的并发

多核处理器越来越普及.有没有一种简单的办法,能够让我们写的软件释放多核的威力?是有的.随着Golang, Erlang, Scala等为并发设计的程序语言的兴起,新的并发模式逐渐清晰.正如过程式编程和面向对象一样,一个好的编程模式有一个极其简洁的内核,还有在此之上丰富的外延.可以解决现实世界中各种各样的问题.本文以GO语言为例,解释其中内核.外延. 并发模式之内核 这种并发模式的内核只需要 协程 和 通道 就够了.协程负责执行代码,通道负责在协程之间传递事件. 不久前,并发编程是个非常困难的事.