go context 包

Go服务器中,每个传入的请求都在其自己的goroutine中处理。
请求处理程序通常会启动其他goroutine来访问后端,例如数据库和RPC服务
处理请求的goroutine集合通常需要访问特定于请求的值,例如最终用户的身份,授权令牌和请求的截止日期。
当请求被取消或超时时,处理该请求的所有goroutine都应该快速退出,以便系统可以回收它们正在使用的任何资源。
go提供了一个’context’包,也就是上下文,来达到控制他们的目的,可以轻松地将请求范围的值,取消信号和API边界的截止日期传递给处理请求所涉及的所有goroutine


####context的使用
在请求处理的过程中,会调用各层的函数,每层的函数会创建自己的routine,构成了一个routine树, 如果由Context来控制上下文,context也应该反映并实现成一棵树

根节点
要创建context树,第一步是要有一个根结点。context.Background函数的返回值是一个空的context,经常作为树的根结点,它一般由接收请求的第一个routine创建,不能被取消、没有值、也没有过期时间

var (
	background = new(emptyCtx)
	todo       = new(emptyCtx)
)

// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
	return background
}

  • 子节点
    context包为我们提供了以下函数,当一个context取消,所有从他衍生的contexts都会被取消

  • context的带的方法

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}
  • Deadline: 返回1个time.time,表示当前context应该结束的时间,ok表示有结束时间
  • Done: context被取消或超时返回close的chan,告诉context相关的函数停止当前工作返回(类似全局广播)
  • Err: context被取消的原因
  • Value: context实现共享数据的地方,是协程安全的

#####Context 所以方法

func Background() Context
func TODO() Context

// ctx, cancel := context.WithCancel(Background())
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

// ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(5 * time.Second)) 5秒之后取消
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

// ctx, cancel := context.WithTimeout(ctx, 10*time.Second)  10秒后取消
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

// ctxVal := context.WithValue(ctx, "hello", "world") 
func WithValue(parent Context, key interface{}, val interface{}) Context

这四个函数的第一个参数都是父context,返回一个Context类型的值,这样就层层创建出不同的节点。子节点是从复制父节点得到的,并且根据接收的函数参数保存子节点的一些状态值,然后就可以将它传递给下层的routine了

#####例子

* 例一:WithCancel()

func main() {
	ctx, cancel := context.WithCancel(context.Background())

	// 用chan实现锁
	c := make(chan bool, 1)

	go worker(ctx, "worker1", c)
	go worker(ctx, "worker2", c)
	go worker(ctx, "worker3", c)

	time.Sleep(5 * time.Second)
	fmt.Println("通知工作停止")
	cancel()
	for i := 0; i < 3; i++ {
		// 接收到3次数据,表明3个worker都关闭
		// 如果没有这个锁,可能cancel()执行后,协程可能没来得及退出,主函数就结束了
		// 比sync.waitGroup好用,wait跟context搭配容易出奇奇怪怪的问题
		<-c
	}
	close(c)
	fmt.Println("所有工作停止")
}

func worker(ctx context.Context, workerId string, c chan bool) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println(workerId, "goroutine stop worked")
			c <- true
			return
		default:
			fmt.Println(workerId, "goroutine is working...")
			time.Sleep(1500 * time.Millisecond)
		}
	}
}
* 例二:WithDeadline

func main() {
	// 截至某某时间,自动取消,也可以用cancel提前取消
	// 当前时间的5秒后,自动取消
	t := time.Now().Add(5 * time.Second)
	ctx, _ := context.WithDeadline(context.Background(), t)

	c := make(chan bool, 1)

	go worker(ctx, "worker1", c)
	go worker(ctx, "worker2", c)
	go worker(ctx, "worker3", c)

	for i := 0; i < 3; i++ {
		<-c
	}
	close(c)
	fmt.Println("所有工作停止")
}

func worker(ctx context.Context, workerId string, c chan bool) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println(workerId, "goroutine stop worked")
			c <- true
			return
		default:
			fmt.Println(workerId, "goroutine is working...")
			time.Sleep(1500 * time.Millisecond)
		}
	}
}
* 例三:WithTimeout

func main() {
	// cancel可以忽略,5秒后自动取消
	ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)

	c := make(chan bool, 1)

	go worker(ctx, "worker1", c)
	go worker(ctx, "worker2", c)
	go worker(ctx, "worker3", c)

	for i := 0; i < 3; i++ {
		<-c
	}
	close(c)
	fmt.Println("所有工作停止")
}

func worker(ctx context.Context, workerId string, c chan bool) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println(workerId, "goroutine stop worked")
			c <- true
			return
		default:
			fmt.Println(workerId, "goroutine is working...")
			time.Sleep(1500 * time.Millisecond)
		}
	}
}
* 例四:WithValue

func main() {
	ctx, cancel := context.WithCancel(context.Background())

	val := "hello world"
	// 由于withValue没有返回cancel,只能作为有cancel节点的子节点
	ctxVal := context.WithValue(ctx, "worker", val)

	c := make(chan bool, 1)

	go worker(ctxVal, c)
	go worker(ctxVal, c)
	go worker(ctxVal, c)

	time.Sleep(5 * time.Second)

	fmt.Println("通知所有工作准备停止")
	cancel()
	for i := 0; i < 3; i++ {
		<-c
	}
	close(c)
	fmt.Println("所有工作已经停止")
}

func worker(ctx context.Context, c chan bool) {
	for {
		select {
		case <-ctx.Done():
			fmt.Println(ctx.Value("worker"), "goroutine stop worked")
			c <- true
			return
		default:
			fmt.Println(ctx.Value("worker"), "goroutine is working...")
			time.Sleep(1500 * time.Millisecond)
		}
	}
}

总结
context包是用来管理进程以及衍生出的协程。打一个比方,一个项目三组人,项目相当于进程,每一组相对于一个线程,组里面每个人相当于协程。
当一个组任务完成了,组员们可以停下你们的工作了。那么现实中就是需要,组长告知大家,所有人停止任务。同样协程也需要,一个信号告诉他们停止任务。
context包就起到通知的功能。项目进程持有一个顶级A Context,每个组持有A Context衍生的B,C,D Context,组成员持有组衍生X,Y,Z Context.

####Context 使用原则
不要把Context放在结构体中,要以参数的方式传递
以Context作为参数的函数方法,应该把Context作为第一个参数,放在第一位。
给一个函数方法传递Context的时候,不要传递nil,如果不知道传递什么,就使用context.TODO
Context的Value相关方法应该传递必须的数据,不要什么数据都使用这个传递
Context是线程安全的,可以放心的在多个goroutine中传递

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值