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
}
Deadline: 返回Context被取消的时间,即截止日期。Done: 返回一个信号Channel,提示Context被取消,Goroutine就可以在Done分支接收到信号时,中断运行。Err: 返回Context被取消的原因,Done分支接收到信号时:- 如果
Context被取消,会返回Canceled错误。 Context超时,会返回DeadlineExceeded错误。
- 如果
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
}
