Golang Context包详解

Golang Context包详解

引言:

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

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()

        for {

                time.Sleep(1 * time.Second)

                fmt.Println("Continue.....")

        }

}

1、context

Context 包提供上下文机制在goroutinue之间传递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.1ConText 类型

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返回flase。该方法可以用于当前是否临近deadline。

Err() error

 

如果Done的channel被关闭了,Err函数会返回一个error,说明错误原因:

1.如果channel是因为被取消而关闭,打印canceled;

2.如果 channel 是因为 deadline 到时了,打印 deadline exceeded。

重复调用,返回相同值。

Value(key interface{}) interface{}

返回由 WithValue 关联到 context 的值。

1.2创建Context

有两种方法创建Context

  1. Context.Background()
  2. 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
}

 

 

package main

 

import (

        "fmt"

        "context"

        "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) //循环5次后

        go sleepRandom_2(ctxChild)

        select {

        case <-stopChan:

                fmt.Println("stopChan received")

        }

        fmt.Println("cancelParent=", cancelParent)

        cancelParent()

        for {

                time.Sleep(1 * time.Second)

                fmt.Println("Continue.....")

        }

}

 

输出结果:

This is sleep Random 1:0

This is Sleep Random 2:0

This is Sleep Random 2:1

This is sleep Random 1:1

This is sleep Random 1:2

This is Sleep Random 2:2

This is sleep Random 1:3

This is Sleep Random 2:3

This is Sleep Random 2:4

This is sleep Random 1:4

cancel sleep random 1

stopChan received

cancelParent= 0x294380

Continue.....

This is Sleep Random 2:5

Why?context canceled

cancel sleep random 2

Continue.....

Continue.....

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值