Skip to main content

Context

Context

Context即上下文,是程序在运行的时候,所有子孙程序都能访问到一个对象,这个对象里有着所有子孙程序都需要的或者部分需要的数据或功能,那么这个对象就是程序的上下文。

Go的Context本质上与上下文的的概念是相同的,但Go的Context内部提供的是传递键值对、超时取消功能等。

创建空上下文

package main

import (
"context"
"fmt"
)

func main() {
ctx1 := context.Background()
ctx2 := context.TODO()

// context.Background
fmt.Println(ctx1)

// context.TODO
fmt.Println(ctx2)
}

Background创建一个需要派生子上下文的根,而TODO创建一个不明确的上下文体系的空上下文。

Context接口

Background方法返回一个空的结构体backgroundCtx

type emptyCtx struct{}
type backgroundCtx struct{ emptyCtx }

func Background() Context {
return backgroundCtx{}
}

返回值的类型为Context接口,Context接口如下:

type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
  1. Deadline: 返回Context被取消的时间,即截止日期。
  2. Done: 返回一个信号Channel,提示Context被取消,Goroutine就可以在Done分支接收到信号时,中断运行。
  3. Err: 返回Context被取消的原因,Done分支接收到信号时:
    • 如果Context被取消,会返回Canceled错误。
    • Context超时,会返回DeadlineExceeded错误。
  4. Value: 可以从同一个Context中获取key对应的值。一般情况下,基本上不会通过Value传递参数之类的数据。

Context派生

可以通过空的Context派生出多种不同的Context:

WithCancel
ctx := context.Background()
cancelCtx, cancelFn := ctx.WithCancel(ctx)
// cancelFn() => (Done <-chan)

cancelFn函数执行会取消上下文,Done会接收到信号。

package main

import (
"context"
"fmt"
"time"
)

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

go reqCancel(cancelCtx)

time.Sleep(3 * time.Second)

// 取消Context
cancel()

time.Sleep(5 * time.Second)
}

func reqCancel(cancelCtx context.Context) {
tick := time.Tick(1 * time.Second)
for range tick {
select {
case <-cancelCtx.Done():
fmt.Println("Request is canceled!!!")
fmt.Println(cancelCtx.Err())
return
default:
fmt.Println("Requesting")
}
}
}

/**
Requesting
Requesting
Requesting
Request is canceled!!!
context canceled
*/
WithTimeout
ctx := context.Background()
timeoutCtx, cancelFn := context.WithTimeout(ctx1, 2*time.Second)
// cancelFn => (Done <-chan)

如上,2秒钟超时后会发送取消信号,Done会接收到信号。

package main

import (
"context"
"fmt"
"time"
)

func main() {
ctx := context.Background()
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)

go reqTimeout(timeoutCtx)

time.Sleep(3 * time.Second)

cancel()

time.Sleep(5 * time.Second)
}

func reqTimeout(timeoutCtx context.Context) {
tick := time.Tick(1 * time.Second)
for range tick {
select {
case <-timeoutCtx.Done():
fmt.Println("Request timeout!!!!")
fmt.Println(timeoutCtx.Err())
return
default:
fmt.Println("Requesting")
}
}
}

当然,在未达到超时时间时,也可提前调用cancelFn函数手动取消。

WithDeadline
ctx := context.Background()
deadlineCtx, cancelFn := context.WithDeadline(ctx, time.Now().Add(2 * time.Second))
// cancelFn -> (Done <-chan)

在到达所设置的时间点后,会发送取消信号,可用于定时任务等。

package main

import (
"context"
"fmt"
"time"
)

func main() {
ctx := context.Background()
deadlineCtx, cancel := context.WithDeadline(ctx, time.Now().Add(3*time.Second))

go reqTimeout(deadlineCtx)

time.Sleep(3 * time.Second)

cancel()

time.Sleep(5 * time.Second)
}

func reqDeadline(deadlineCtx context.Context) {
tick := time.Tick(1 * time.Second)
for range tick {
select {
case <-deadlineCtx.Done():
mt.Println("Request deadline!!!")
mt.Println(deadlineCtx.Err())
return
default:
fmt.Println("Requesting")
}
}
}

在未达到具体时间点是,也可调用cancelFn提前取消。

WithValue
ctx := context.Background()
valueCtx := ctx.WithValue(ctx, "name", "Tom")
// valueCtx.Value() => Tom

考虑如下示例:

package main

import (
"context"
"fmt"
"time"
)

func main() {
ctx := context.Background()
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
valueCtx := context.WithValue(timeoutCtx, "name", "Tom")

go reqValue(valueCtx)

time.Sleep(3 * time.Second)

cancel()

time.Sleep(5 * time.Second)
}

func reqValue(valueCtx context.Context) {
tick := time.Tick(1 * time.Second)
for range tick {
select {
case <-valueCtx.Done():
fmt.Println("Request timeout!!!!~~~~")
fmt.Println(valueCtx.Err())
return
default:
fmt.Println(valueCtx.Value("name"))
}
}
}

Context树

Context之间可形成树形结构

package main

import (
"context"
"fmt"
"time"
)

func main() {
ctx := context.Background()

ctx1, _ := context.WithTimeout(ctx, 5*time.Second)
ctx2 := context.WithValue(ctx1, "name", "Tom")
ctx3, _ := context.WithDeadline(ctx2, time.Now().Add(1*time.Second))

fmt.Println(ctx) // context.Background
fmt.Println(ctx1) // context.Background.WithDeadline
fmt.Println(ctx2) // context.Background.WithDeadline.WithValue
fmt.Println(ctx3) // context.Background.WithDeadline.WithValue.WithDeadline

ctx4, _ := context.WithTimeout(ctx, 5*time.Second)
fmt.Println(ctx4) // context.Background.WithDeadline
}

Context树形结构