Golang Context 包详解
Golang Context 包详解
0. 引言
在 Go 语言编写的服务器程序中,服务器通常要为每个 HTTP 请求创建一个 goroutine 以并发地处理业务。同时,这个 goroutine 也可能会创建更多的 goroutine 来访问数据库或者 RPC 服务。
当这个请求超时或者被终止的时候,需要优雅地退出所有衍生的 goroutine,并释放资源。因此,我们需要一种机制来通知衍生 goroutine 请求已被取消。 比如以下例子,sleepRandom_1 的结束就无法通知到 sleepRandom_2。
package main
import (
"fmt"
"time"
)
func sleepRandom_1() {
i := 0
for {
time.Sleep(1 * time.Second)
fmt.Printf("This is sleep Random 1: %d\n", i)
i++
if i == 5 {
fmt.Println("cancel sleep random 1")
break
}
}
}
func sleepRandom_2() {
i := 0
for {
time.Sleep(1 * time.Second)
fmt.Printf("This is sleep Random 2: %d\n", i)
i++
}
}
func main() {
go sleepRandom_1() // 循环 5 次后退出
go sleepRandom_2() // 会一直打印 This is sleep Random 2
for {
time.Sleep(1 * time.Second)
fmt.Println("Continue...")
}
}
1. Context
Context 包提供上下文机制在 goroutine 之间传递 deadline、取消信号(cancellation signals)或者其他请求相关的信息。使用方法是:
- 首先,服务器程序为每个接受的请求创建一个 Context 实例(称为根 context,通过
context.Background()方法创建); - 之后的 goroutine 接受根 context 的一个派生 Context 对象。比如通过调用根 context 的 WithCancel 方法,创建子 context;
- goroutine 通过
context.Done()方法监听取消信号。func Done() <-chan struct{}是一个通信操作,会阻塞 goroutine,直到收到取消信号接触阻塞。
(可以借助 select 语句,如果收到取消信号,就退出 goroutine;否则,默认子句是继续执行 goroutine); - 当一个 Context 被取消(比如执行了
cancelFunc()),那么该 context 派生出来的 context 也会被取消。
1.1 Context 类型
// A Context carries a deadline, a cancelation signal, and other values across
// API boundaries.
//
// Context's methods may be called by multiple goroutines simultaneously.
type Context interface {
Done() <-chan struct{}
Deadline() (deadline time.Time, ok bool)
Err() error
Value(key interface{}) interface{}
}
Done() <-chan struct{}
Done 方法返回一个 channel,阻塞当前运行的代码,直到以下条件之一发生时,channel 才会被关闭,进而解除阻塞:
- WithCancel 创建的 context,cancelFunc 被调用。该 context 以及派生子 context 的 Done channel 都会收到取消信号;
- WithDeadline 创建的 context,deadline 到期。
- WithTimeout 创建的 context,timeout 到期
Done 要配合 select 语句使用:
// DoSomething 生产数据并发送给通道 out
// 但如果 DoSomething 返回一个则退出函数,
// 或者 ctx.Done 被关闭时也会退出函数.
func Stream(ctx context.Context, out chan<- Value) error {
for {
v, err := DoSomething(ctx)
if err != nil {
return err
}
select {
case <-ctx.Done():
return ctx.Err()
case out <- v:
}
}
}
Deadline() (deadline time.Time, ok bool)
WithDeadline 方法会给 context 设置 deadline,到期自动发送取消信号。调用 Deadline() 返回 deadline 的值。如果没设置,ok 返回 false。
该方法可用于确定当前时间是否临近 deadline。
Err() error
如果 Done 的 channel 被关闭了, Err 函数会返回一个 error,说明错误原因:
- 如果 channel 是因为被取消而关闭,打印 canceled;
- 如果 channel 是因为 deadline 到时了,打印 deadline exceeded。
重复调用,返回相同值。
Value(key interface{}) interface{}
返回由 WithValue 关联到 context 的值。
1.2 创建根 Context
有两种方法创建根 Context:
- context.Background()
- context.TODO()
根 context 不会被 cancel。这两个方法只能用在最外层代码中,比如 main 函数里。一般使用 Background() 方法创建根 context。
TODO() 用于当前不确定使用何种 context,留待以后调整。
1.3 派生 Context
一个 Context 被 cancel,那么它的派生 context 都会收到取消信号(表现为 context.Done() 返回的 channel 收到值)。
有四种方法派生 context :
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)func WithValue(parent Context, key, val interface{}) Context
WithCancel
最常用的派生 context 方法。该方法接受一个父 context。父 context 可以是一个 background context 或其他 context。
返回的 cancelFunc,如果被调用,会导致 Done channel 关闭。因此,绝不要把 cancelFunc 传给其他方法。
WithDeadline
该方法会创建一个带有 deadline 的 context。当 deadline 到期后,该 context 以及该 context 的可能子 context 会受到 cancel 通知。
另外,如果 deadline 前调用 cancelFunc 则会提前发送取消通知。
WithTimeout
与 WithDeadline 类似。创建一个带有超时机制的 context。
WithValue
WithValue 方法创建一个携带信息的 context,可以是 user 信息、认证 token等。该 context 与其派生的子 context 都会携带这些信息。
WithValue 方法的第二个参数是信息的唯一 key。该 key 类型不应对外暴露,为了避免与其他包可能的 key 类型冲突。所以使用 WithValue 也
应像下面例子的方式间接调用 WithValue。
WithValue 方法的第三个参数即是真正要存到 context 中的值。
使用 WithValue 的例子:
package user
import "context"
// User 类型对象会被保存到 Context 中
type User struct {
// ...
}
// key 不应该暴露出来。这样避免与包中其他 key 类型冲突
type key int
// userKey 是 user 的 key,不应暴露;
// 通过 user.NewContext 和 user.FromContext 间接使用 key
var userKey key
// NewContext 返回携带 u 作为 value 的 Context
func NewContext(ctx context.Context, u *User) context.Context {
return context.WithValue(ctx, userKey, u)
}
// FromContext 返回关联到 context 的 User类型的 value 的值
func FromContext(ctx context.Context) (*User, bool) {
u, ok := ctx.Value(userKey).(*User)
return u, ok
}
2. 例子
改进引子里的例子。 sleepRandom_1 结束后,会触发 cancelParent() 被调用。所以 sleepRandom_2 中的 ctx.Done() 会被关闭。
sleepRandom_2 执行退出。
package main
import (
"context"
"fmt"
"time"
)
func sleepRandom_1(stopChan chan struct{}) {
i := 0
for {
time.Sleep(1 * time.Second)
fmt.Printf("This is sleep Random 1: %d\n", i)
i++
if i == 5 {
fmt.Println("cancel sleep random 1")
stopChan <- struct{}{}
break
}
}
}
func sleepRandom_2(ctx context.Context) {
i := 0
for {
time.Sleep(1 * time.Second)
fmt.Printf("This is sleep Random 2: %d\n", i)
i++
select {
case <-ctx.Done():
fmt.Printf("Why? %s\n", ctx.Err())
fmt.Println("cancel sleep random 2")
return
default:
}
}
}
func main() {
ctxParent, cancelParent := context.WithCancel(context.Background())
ctxChild, _ := context.WithCancel(ctxParent)
stopChan := make(chan struct{})
go sleepRandom_1(stopChan)
go sleepRandom_2(ctxChild)
select {
case <- stopChan:
fmt.Println("stopChan received")
}
cancelParent()
for {
time.Sleep(1 * time.Second)
fmt.Println("Continue...")
}
}
3. 参考文档
Go Concurrency Patterns: Context
Understanding the context package in golang
Golang Context 包详解的更多相关文章
- 【GoLang】golang context channel 详解
代码示例: package main import ( "fmt" "time" "golang.org/x/net/context" ) ...
- golang context用法详解
背景 在go服务器中,对于每个请求的request都是在单独的goroutine中进行的,处理一个request也可能设计多个goroutine之间的交互, 使用context可以使开发者方便的在这些 ...
- Golang官方log包详解
Golang官方log包详解 以下全是代码, 详解在注释中, 请从头到尾看 // Copyright 2009 The Go Authors. All rights reserved. // Use ...
- Spring jar包详解
Spring jar包详解 org.springframework.aop ——Spring的面向切面编程,提供AOP(面向切面编程)的实现 org.springframework.asm——spri ...
- Spring——jar包详解(转)
Spring——jar包详解 org.springframework.aop ——Spring的面向切面编程,提供AOP(面向切面编程)的实现 org.springframework.asm——spr ...
- Spring 3.x jar 包详解 与 依赖关系
以下的内容我会持续更新(当然是我有新发现的时候); 以下内容是我在网上搜索.整理.修改的而成的内容.由于很多内容都是转载了,无法追溯到源头,因此无法一一对原作者进行道谢. 这几天,我查阅大量的官方的文 ...
- Spring 3.x jar 包详解 与 依赖关系(转)
以下的内容我会持续更新(当然是我有新发现的时候); 以下内容是我在网上搜索.整理.修改的而成的内容.由于很多内容都是转载了,无法追溯到源头,因此无法一一对原作者进行道谢. 这几天,我查阅大量的官方的文 ...
- 常见 jar包详解
常见 jar包详解 jar包 用途 axis.jar SOAP引擎包 commons-discovery-0.2.jar 用来发现.查找和实现可插入式接口,提供一些一般类实例化.单件的生命周期 ...
- spring原理案例-基本项目搭建 02 spring jar包详解 spring jar包的用途
Spring4 Jar包详解 SpringJava Spring AOP: Spring的面向切面编程,提供AOP(面向切面编程)的实现 Spring Aspects: Spring提供的对Aspec ...
随机推荐
- Java:Web Service初入门
前言 Web Service技术在我第一次接触,又没有实际使用时完全不理解这是什么.以为是一种类似Spring,Shiro的编程框架.后来渐渐理解,WS(即Web Service缩写)是一种通用的接口 ...
- 订Pizza(Java)
帮朋友改的一个订pizza的作业 大概要求就是输入判断,选择pizza的个数和种类,然后返回一个价格 代码放在下面,如果有刚学Java的同学可以参考一下,没有什么难度 public class Piz ...
- Jsoup配合 htmlunit 爬取异步加载的网页
加入 jsoup 和 htmlunit 的依赖 <dependency> <groupId>org.jsoup</groupId> <artifactId&g ...
- Qt实现表格控件-支持多级列表头、多级行表头、单元格合并、字体设置等
目录 一.概述 二.效果展示 三.定制表头 1.重写数据源 2.重写QHeaderView 四.设置属性 五.相关文章 原文链接:Qt实现表格控件-支持多级列表头.多级行表头.单元格合并.字体设置等 ...
- 跟我学SpringCloud | 第十二篇:Spring Cloud Gateway初探
SpringCloud系列教程 | 第十二篇:Spring Cloud Gateway初探 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如 ...
- (转)User-Agent的由来(原来这么有意思)
你是否好奇标识浏览器身份的User-Agent,为什么每个浏览器都有Mozilla字样?Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 ( ...
- Bzoj1972: [Sdoi2010]猪国杀 题解(大模拟+耐心+细心)
猪国杀 - 可读版本 https://mubu.com/doc/2707815814591da4 题目可真长,读题都要一个小时. 这道题很多人都说不可做,耗时间,代码量大,于是,本着不做死就不会死的精 ...
- 【小家Spring】老项目迁移问题:@ImportResource导入的xml配置里的Bean能够使用@PropertySource导入的属性值吗?
#### 每篇一句 > 大师都是偏执的,偏执才能产生力量,妥协是没有力量的.你对全世界妥协了你就是空气.所以若没有偏见,哪来的大师呢 #### 相关阅读 [[小家Spring]详解Propert ...
- CAD2014学习笔记-图纸布局和打印输出
基于 虎课网huke88.com CAD教程 图纸设计规范:施工图 封面设计:地点.名称.设计人 目录设计:施工图编号.名称.意义.对应页数.注释.图号序号:包括平面.立面.大样图.施工图 设计说明/ ...
- ADO.NET_包括DataReader和dataSet的使用
今天总结了一下ADO.NET编程中DataReader和dataSet两个比较重要的对象的使用,完成了combobox,listbox,以及fpSpread动态添加数据的测试,对使用sqlComman ...