九. Go并发编程--context.Context (4)

Value 的查找和之前 cancelCtx 类似,都是先判断当前有没有,没有就向上递归,只是在 cancelCtx 当中 key 是一个固定的 key 而已

func (c *valueCtx) Value(key interface{}) interface{} { if c.key == key { return c.val } return c.Context.Value(key) }

Value 就没有实现 Context 接口的其他方法了,其他的方法全都是复用的 parent context 的方法

3. 使用案例 三. 使用案例 3.1 使用cancel context package main import ( "context" "fmt" "time" ) // 模拟请求 func HandelRequest(ctx context.Context) { // 模拟讲数据写入redis go WriteRedis(ctx) // 模拟讲数据写入数据库 go WriteDatabase(ctx) for { select { case <-ctx.Done(): fmt.Println("HandelRequest Done.") return default: fmt.Println("HandelRequest running") time.Sleep(2 * time.Second) } } } func WriteRedis(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("WriteRedis Done.") return default: fmt.Println("WriteRedis running") time.Sleep(2 * time.Second) } } } func WriteDatabase(ctx context.Context) { // for 循环模拟间歇性尝试 for { select { case <-ctx.Done(): fmt.Println("WriteDatabase Done.") return default: fmt.Println("WriteDatabase running") time.Sleep(2 * time.Second) } } } func main() { ctx, cancel := context.WithCancel(context.Background()) go HandelRequest(ctx) // 模拟耗时 time.Sleep(5 * time.Second) fmt.Println("It's time to stop all sub goroutines!") cancel() // Just for test whether sub goroutines exit or not time.Sleep(5 * time.Second) }

上面代码中协程HandelRequest()用于处理某个请求,其又会创建两个协程:WriteRedis()、WriteDatabase(),main协程创建context,并把context在各子协程间传递,main协程在适当的时机可以cancel掉所有子协程。

程序输出如下所示:

HandelRequest running WriteRedis running WriteDatabase running HandelRequest running WriteRedis running WriteDatabase running WriteRedis running WriteDatabase running HandelRequest running It's time to stop all sub goroutines! WriteDatabase Done. HandelRequest Done. WriteRedis Done. 3.2 WithTimeout 使用

用WithTimeout()获得一个context并在其子协程中传递:

package main import ( "context" "fmt" "time" ) func HandelRequest2(ctx context.Context) { go WriteRedis2(ctx) go WriteDatabase2(ctx) for { select { case <-ctx.Done(): fmt.Println("HandelRequest Done.") return default: fmt.Println("HandelRequest running") time.Sleep(2 * time.Second) } } } func WriteRedis2(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("WriteRedis Done.") return default: fmt.Println("WriteRedis running") time.Sleep(2 * time.Second) } } } func WriteDatabase2(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("WriteDatabase Done.") return default: fmt.Println("WriteDatabase running") time.Sleep(2 * time.Second) } } } func main() { // 定义超时时间,当超过定义的时间后会自动执行 context cancel, 从而终止字写成 ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) go HandelRequest2(ctx) // 模拟阻塞等待防止主线程退出 time.Sleep(10 * time.Second) }

主协程中创建一个5s超时的context,并将其传递给子协程,10s自动关闭context。程序输出如下:

HandelRequest running WriteDatabase running WriteRedis running # 循环第一次 耗时1s WriteRedis running # 循环第二次 耗时3s WriteDatabase running HandelRequest running HandelRequest running WriteRedis running # 循环第三次 耗时5s WriteDatabase running WriteRedis Done. HandelRequest Done. WriteDatabase Done. 3.3 Value值传递

下面示例程序展示valueCtx的用法:

package main import ( "context" "fmt" "time" ) func HandelRequest3(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("HandelRequest Done.") return default: fmt.Println("HandelRequest running, parameter: ", ctx.Value("parameter")) time.Sleep(2 * time.Second) } } } func main() { // 取消时,将父 context value值传递给子context ctx := context.WithValue(context.Background(), "parameter", "1") go HandelRequest3(ctx) time.Sleep(10 * time.Second) }

输出:

HandelRequest running, parameter: 1 HandelRequest running, parameter: 1 HandelRequest running, parameter: 1 HandelRequest running, parameter: 1 HandelRequest running, parameter: 1

上例main()中通过WithValue()方法获得一个context,需要指定一个父context、key和value。然后通将该context传递给子协程HandelRequest,子协程可以读取到context的key-value。

注意:本例中子协程无法自动结束,因为context是不支持cancle的,也就是说<-ctx.Done()永远无法返回。

如果需要返回,需要在创建context时指定一个可以cancel的context作为父节点,使用父节点的cancel()在适当的时机结束整个context。

3.4 错误取消

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgzjys.html