在Go里, 我们不能直接杀死协程, 协程的关闭一般会用channel+select方式/或者runtime.Goexit()来控制. 虽然能打到目的,但是如果在复杂派生场景下,就会显得乏力了. 于是就引出了Context:
context接口
1.分类
1.可被控制类
1.emptyCtx{backgroundCtx,todoCtx} ps:不可取消,没有设置截止时间,没有携带任何值的Ctx
2.不可被控制类
1.cancelCtx{cancelCtx}
2.timerCtx{deadlineCtx,timeoutCtx}
3.valueCtx{valueCtx}
2.接口
1.context接口(所有ctx都实现了)
1.deadline方法
返回ctx的deadline,以及ctx是否会被取消;没有设置deadline的话,ok==false
2.done方法
ctx被取消后,返回一个被关闭的channel
3.err方法
返回ctx被取消的原因
4.value方法
返回参数key对应的value
2.canceler接口(只有cancelCtx和timerCtx实现了)
实现了这个接口表明该ctx是可以被取消的
# context接口
四个方法:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Donne() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
1.Deadline()
func (context.Context).Deadline() (deadline time.Time, ok bool)
deadline: 返回当前Context被取消的时间
ok: 如果没有设置deadline,则返回ok==false 即: 不会被取消
2. Done()
func (context.Context).Done() <-chan struct{}
context被取消的信号(是否执行cancelFunc):
1. 执行cancelFunc (即 context被取消):
将返回一个关闭的channel(),select读取分支从这个关闭的channel读取除了零值(nil)
读取到了零值就代表context被关闭,于是就执行退出协程.
2. 不执行cancelFunc (即context不会被取消):
将返回一个nil(什么也不返回,更没有channel),
返回一个nil就代表没有channel可以读, 那么select就直接进入default分支执行!
3. Err()
func (context.Context).Err() error
返回一个错误,表示context被关闭的原因:
1. context未被关闭,返回nil
2. context被关闭,返回一个非nil错误:
2.1.Context被取消 -- Canceled;
2.2.Context超时 -- DeadlineExceeded;
4. Value()
获取之前设置的key所对应的value
用法
1.创建根ctx
根ctx的类型:
1.context.Background()获取background类型ctx
2.context.Todu()获取todo类型ctx
2.创建子ctx
子ctx的类型:
1.caccelCtx
1.withcancel
实现接口: context,canceler
创建方法:context.WithCancel(父Ctx)(withcancelCtx, 当前ctx的取消函数)
2.timerCtx(timerCtx基于cancelCtx,只是多了一个time.Timer和一个deadline)
1.deadlineCtx
实现接口: context,canceler
创建方法:context.WithDeadline(父Ctx)...
2.timeoutCtx
实现接口: context,canceler
创建方法:context.WithTimeout(父Ctx)...
3.valueCtx
1.valueCtx
实现接口: context
创建方法:context.WithValue(父Ctx)...
案例
package main
import (
"context"
"fmt"
"time"
)
/*
func context.WithCancel(parent context.Context) (ctx context.Context, cancel context.CancelFunc)
参数:
parent: 父Context,可以使用background或者TODO作为根Context!
返回值:
ctx: 返回根据父Context创建的子Context
cancel: 取消函数
*/
func main() {
// 1. 根ctx
ctx0 := context.Background()
// 2. 创建子Ctx,类型为withcancel
ctx1, cancel1 := context.WithCancel(ctx0)
// 3. 传入子withcancel到go1协程中
go go1(ctx1)
// 4. 创建孙子Ctx,类型为withcancel
ctx2, cancel2 := context.WithCancel(ctx1)
_ = cancel2
// 5. 传入孙子withcancel到go2协程中
go go2(ctx2)
// 6. 取消ctx1
time.Sleep(time.Second * 22)
cancel1() // 当父Context关闭的时候,其派生的所有子Context也会被关闭!
// 7.查看ctx1被关闭的原因
err := ctx1.Err()
if err != nil {
fmt.Printf("%v: levelOne_context被关闭的原因:%v\n", time.Now().UTC().Unix(), err)
}
}
// go1
func go1(ctx context.Context) {
count := 1
for {
select {
case <-ctx.Done(): // 接收ctx关闭信号
default: // 执行任务
time.Sleep(time.Second * 5)
fmt.Printf("%v: levelOne_goroutine正在执行%d次任务!\n", time.Now().UTC().Unix(), count)
count++
}
}
}
// go2
func go2(ctx context.Context) {
count := 1
for {
select {
case <-ctx.Done(): // 返回关闭的chan --> 关闭当前协程 (啥都不需要做,goroutine会自动关闭)
default: // 返回nil --> 执行goroutine的任务
time.Sleep(time.Second * 5)
fmt.Printf("%v: levelTwo_gotoutine正在执行%d次任务!\n", time.Now().UTC().Unix(), count)
count++
}
}
}
2314

被折叠的 条评论
为什么被折叠?



