什么是context
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
Context本质上是一个接口,完成这四个办法的都能够被称作context
Deadline()
回来time.Time类型的context过期时刻,和一个布尔值ok。若ok为false则该context未设置过期时刻
c := context.Background() //创立个空context
fmt.Println(c.Deadline())
c1, cancel := context.WithTimeout(c, 3*time.Second) //加上时刻约束,回来一个context和一个撤销函数
defer cancel()
fmt.Println(c1.Deadline())
//输出
0001-01-01 00:00:00 +0000 UTC false
2023-11-28 21:38:59.2174603 +0800 CST m=+3.002624401 true
Done()
回来一个channel,当context封闭时,channel会封闭。假如context永久不会封闭,则会回来nil
c := context.Background() //创立个空context
fmt.Println(c.Done())
c1, cancel := context.WithTimeout(c, 3*time.Second) //加上时刻约束,回来一个context和一个撤销函数
cancel()
select {
case <-c1.Done():
fmt.Println("context过期,channel封闭")
}
//输出
<nil>
context过期,channel封闭
Err()
回来一个error类型过错,没过错则回来nil,一般只要context超时和被封闭时则会回来error
c := context.Background() //创立个空context
c1, _ := context.WithTimeout(c, 1*time.Second) //加上时刻约束,回来一个context和一个撤销函数
fmt.Println(c1.Err())
time.Sleep(2*time.Second)
fmt.Println(c1.Err())
c2, cancel := context.WithTimeout(c, 3*time.Second)
cancel()
fmt.Println(c2.Err())
//输出 (context超时和被撤销的回来的error不相同)
<nil>
context deadline exceeded
context canceled
Value(key any)
类似map,输入key给出对应value。key通常在全局变量中分配,可回来任何类型的值。屡次调用仍会回来相同值
c := context.Background() //创立个空context
c1 := context.WithValue(c, "Jack", "Rose") //在context中设置一个键值对
fmt.Println(c1.Value("Jack"))
fmt.Println(c1.Value("Jack"))
fmt.Println(c1.Value("Jack"))
//输出
Rose
Rose
Rose
过错回来
撤销过错
var Canceled = errors.New("context canceled")
当context被cancel函数封闭时调用Err()
就会回来该过错
超时过错
type deadlineExceededError struct{}
func (deadlineExceededError) Error() string { return "context deadline exceeded" }
func (deadlineExceededError) Timeout() bool { return true }
func (deadlineExceededError) Temporary() bool { return true }
此过错被调集成了一个结构体,经过调用其的三个办法来反映过错和获取信息
EmptyContext
最简略的一个context
没有过期时刻和信息,用来当作父context或许其他需求,经过其不断延伸
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key any) any {
return nil
}
emptyCtx其他办法
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}
Background和Todo
都是emptyCtx,本质上是相同,仅仅人为区分他们,将他们用作不同途径。
- Background 回来一个非 nil、空的 Context。它永久不会被撤销,没有价值,也没有截止日期。它通常由 main 函数、初始化和测试运用,并用作传入请求的顶级 Context。
- TODO 回来一个非 nil 的空 Context。代码应运用上下文。当不清楚要运用哪个 Context 或尚不可用时,就运用 TODO(由于周围函数没有扩展为接受 Context 参数)。
String()
该办法用来判别该emptyCtx是Background仍是Todo
cancel相关
type CancelFunc func()
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := withCancel(parent)
return c, func() { c.cancel(true, Canceled, nil) }
}
type CancelCauseFunc func(cause error)
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) {
c := withCancel(parent)
return c, func(cause error) { c.cancel(true, Canceled, cause) }
}
CancelFunc与CancelCauseFunc
- CancelFunc不需求参数
- CancelCauseFunc需求error类型的参数
他们都是函数的别名,起别名是为了方便后边阅览和运用
WithCancel与WithCancelCause
- 区别是Cause的带着
- 他们经过withCancel创立一个context,并回来该context和撤销函数,下面以WithCancelCause为例,他回来如下的函数
func(cause error) { c.cancel(true, Canceled, cause) }
cause是过错原因,然后这个函数调用新创立的cancelCtx的cancel办法,完成了撤销操作。cancel需求参数如下,上面传入的第二个参数是Canceled
,便是从前界说的一个过错"context canceled"
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error)
后边再细讲cancel
创立cancelCtx
func withCancel(parent Context) *cancelCtx {
if parent == nil {
panic("cannot create context from nil parent")
}
c := newCancelCtx(parent)
propagateCancel(parent, c)
return c
}
此函数需求一个父context,假如传入的是nil则会panic掉
持续往下看会发现呈现新的函数newCancelCtx
,咱们看看它会传给咱们什么
func newCancelCtx(parent Context) *cancelCtx {
return &cancelCtx{Context: parent}
}
它给咱们回来了一个包括父context的cancelCtx
的指针,那么cancelCtx
长什么样呢,持续跟曩昔
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done atomic.Value
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
cause error // set to non-nil by the first cancel call
}
能够看见里面有
-
mu
:锁,用来维护临界资源 -
done
:atomic包里的value类型的值,原子性咱们直接看看源码
//Value 提供原子加载和共同类型化值的存储。
//Value 的零值从 Load 回来 nil。调用 Store 后,不得仿制 Value。首次运用后不得仿制 Value。
type Value struct {
v any
}
-
children
:字段是一个map,用于保存这个context派生出去的一切子context和它们对应的canceler。其间,canceler
接口界说如下:
type canceler interface {
cancel(removeFromParent bool, err error)
}
canceler
接口中有办法cancel
,用于撤销与之关联的context。当一个context的父context被撤销时,它会调用自己的cancel
办法将自己也撤销掉,并将自己从父context的children
字段中移除。
因而,cancelCtx
中的children
字段实际上是用来记录这个context的一切子context以及它们对应的canceler
对象。当这个context被撤销时,它会遍历一切的子context并调用它们的cancel
办法,以便将它们也一起撤销掉。(可是每个cancelCtx本身就满意了canceler接口的条件,也便是说他们自己便是canceler类型,不是很了解详细作用)
-
err
:过错信息 -
cause
:过错原因(差不多吧这两个)
现在咱们现已知道什么是cancelCtx了,那么回到原函数withCanel
上来,newCancelCtx
之后是propagateCancel
函数,它的作用是将child添加到parent的children里面,让咱们看看它的源码
func propagateCancel(parent Context, child canceler) {
done := parent.Done() //获取父context的channel来检测父context是否能被撤销
//下面都用parent指代父context
if done == nil {
return // parent永久无法撤销则退出
}
select {
case <-done: //监听
// parent 现已被撤销
child.cancel(false, parent.Err(), Cause(parent)) //则child调用本身cancel办法撤销自己
return
default:
}
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
// parent has already been canceled
child.cancel(false, p.err, p.cause)
} else {
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
goroutines.Add(1)
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err(), Cause(parent))
case <-child.Done():
}
}()
}
}
第16行又呈现了parentCancelCtx
函数,该函数作用是查找最近的父cancelCtx
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
done := parent.Done()
if done == closedchan || done == nil {
return nil, false
}
p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
if !ok {
return nil, false
}
pdone, _ := p.done.Load().(chan struct{})
if pdone != done {
return nil, false
}
return p, true
}
closedchan
在源码中是这样界说的,所以正如其名,他是个封闭的channel
var closedchan = make(chan struct{})
func init() {
close(closedchan)
}
所以函数中假如parent.Done()回来的是封闭的channel或许nil,说明最近cancelCtx现已被封闭,就回来nil和false
之后再经过parent.Value(&cancelCtxKey)并进行类型断语获取值,可是cancelCtxKey在包中只要潦草的界说
var cancelCtxKey int
是什么含义呢?
咱们再去看看Value办法是怎样处理这个的
经过查找咱们发现cancelCtx重写了Value
func (c *cancelCtx) Value(key any) any {
if key == &cancelCtxKey {
return c
}
return value(c.Context, key)
}
当&cancelCtxKey
作为参数则会回来该cancelCtx
可是func (c *cancelCtx) Value(key any) any
是如何获取到最近的canncelCtx呢?
假如parent本身是cancelCtx,则直接回来该parent,它便是最近的canncelCtx
假如不是,他会不断地调用上一级的value办法直到遇到canncelCtx回来停止,详见如下
func value(c Context, key any) any {
for {
switch ctx := c.(type) {
case *valueCtx:
if key == ctx.key {
return ctx.val
}
c = ctx.Context
case *cancelCtx:
if key == &cancelCtxKey {
return c
}
c = ctx.Context
case *timerCtx:
if key == &cancelCtxKey {
return ctx.cancelCtx
}
c = ctx.Context
case *emptyCtx:
return nil
default:
return c.Value(key)
}
}
}
现在让咱们回到parentCancelCtx
函数上来
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
done := parent.Done()
if done == closedchan || done == nil {
return nil, false
}
p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
if !ok {
return nil, false
}
pdone, _ := p.done.Load().(chan struct{})
if pdone != done {
return nil, false
}
return p, true
}
此刻现现已过parent.Value获取到了最近的cancelCtx并传给变量p了(没有找到就return nil了)
然后,经过p.done.Load()拿到一个管道(p.done是atomic.Value类型,在atomic包里有它的Load办法),
现在讲拿到的管道(最近的cancelCtx的管道)和之前的管道作比较(parent的管道),假如不是同一个就回来nil,这个状况代表你找到了最近的自界说cancelCtx可是并不是包界说的cancelCtx
当一切都判定曩昔后,咱们就成功拿到了最近的cancelCtx,现在咱们总算能够回到propagateCancel
函数了
func propagateCancel(parent Context, child canceler) {
done := parent.Done() //获取父context的channel来检测父context是否能被撤销
//下面都用parent指代父context
if done == nil {
return // 假如分支上不存在可cancel的context则退出
}
select {
case <-done: //监听
// parent 现已被撤销
child.cancel(false, parent.Err(), Cause(parent)) //则child调用本身cancel办法撤销自己
return
default:
}
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
// parent has already been canceled
child.cancel(false, p.err, p.cause)
} else {
if p.children == nil {
p.children = make(map[canceler]struct{})
}
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
goroutines.Add(1)
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err(), Cause(parent))
case <-child.Done():
}
}()
}
}
拿到后先上个锁,再将child放到children的key里
那map中的值struct{}是拿来干什么?
实际上是由于运用空结构体struct{}
作为值的优点在于它占用极少的内存空间,实际上不占用任何空间。这是由于在Go语言中,空结构体的巨细是0字节。经过将空结构体作为值,咱们能够完成一个只关注键的调集,而不需求额外的内存开销。
假如没有拿到,则开启一个协程来监听parent和child管道状况,若parent撤销则child撤销掉自己,若child先撤销则不做为,当两个都没撤销掉这个协程就会一向堵塞在这里,直到其间一个先cancel掉
goroutines.Add(1)
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err(), Cause(parent))
case <-child.Done():
}
}()
到此停止,一个cancelCtx就被成功创立出来了
removeChild
直接看看源码,它的作用是,从最近的父cancelCtx的children中移除child
func removeChild(parent Context, child canceler) {
p, ok := parentCancelCtx(parent)
if !ok {
return
}
p.mu.Lock()
if p.children != nil {
delete(p.children, child)
}
p.mu.Unlock()
}
传入一个父Ctx(不知道类型),然后根据此ctx查找最近的父cancelCtx,没有找到就return
找到就调用其锁,删掉children中的child这个key,再解锁
canceler界说
type canceler interface {
cancel(removeFromParent bool, err, cause error)
Done() <-chan struct{}
}
有cancel办法(后边讲)和Done办法的都是canceler,能够看到咱们一切的cancelCtx都满意这个条件,所以每个cancelCtx实际上也是一个canceler
cancelCtx
cancelCtx的界说在之前现已提到过了,咱们首要讲讲cancelCtx重写父Ctx的办法,就在代码旁批注解说
type cancelCtx struct {
Context
mu sync.Mutex
done atomic.Value
children map[canceler]struct{}
err error
cause error
}
//假如传进来的key是完成设立的cancelCtxKey则回来该cancelCtx本身
//假如不是就会一向往上找,调用该ctx存储的父ctx的信息查看key对应value的值。
func (c *cancelCtx) Value(key any) any {
if key == &cancelCtxKey {
return c
}
return value(c.Context, key)
}
func (c *cancelCtx) Done() <-chan struct{} {
//done是atomic.Value类型,担任原子性存储
//先把done里的东西经过Load取出来
d := c.done.Load()
if d != nil {
return d.(chan struct{})
}
//假如done里啥都没有就上锁(关门打狗)
c.mu.Lock()
defer c.mu.Unlock()
//再次调用Load读取,目的是再次查看context有无被撤销
d = c.done.Load()
//done里确实啥也没有,就给他创立一个,然后存进去
if d == nil {
d = make(chan struct{})
c.done.Store(d)
}
return d.(chan struct{})
}
//加锁读取err
func (c *cancelCtx) Err() error {
c.mu.Lock()
err := c.err
c.mu.Unlock()
return err
}
String接口有关
type stringer interface {
String() string
}
以上为接口界说,在各种string办法中均有contextName函数的呈现,让咱们看看这是什么吧
func contextName(c Context) string {
if s, ok := c.(stringer); ok {
return s.String()
}
return reflectlite.TypeOf(c).String()
}
这个函数将传入的c
做类型断语,假如c
是stringer
接口类型就调用c
的String
办法
假如不是就回来用字符串表明的c
的类型
func (c *cancelCtx) String() string {
return contextName(c.Context) + ".WithCancel"
}
func (c *timerCtx) String() string {
return contextName(c.cancelCtx.Context) + ".WithDeadline(" +
c.deadline.String() + " [" +
time.Until(c.deadline).String() + "])"
}
func (c *valueCtx) String() string {
return contextName(c.Context) + ".WithValue(type " +
reflectlite.TypeOf(c.key).String() +
", val " + stringify(c.val) + ")"
}
以上三个String办法都是回来字符串类型的Ctx的信息
cancel函数
这个函数现已在之前呈现很屡次了,现在咱们来详细讲讲
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {
//假如没有传入err,则panic(可能是误操作的cancel,呈现重大问题,直接panic掉)
if err == nil {
panic("context: internal error: missing cancel error")
}
//假如传入了err可是没有cause,就把err赋值给cause,原因便是err
if cause == nil {
cause = err
}
//之后要对Ctx里的数据操作,先上把锁
c.mu.Lock()
//假如Ctx现已被cancel掉就开锁退出
if c.err != nil {
c.mu.Unlock()
return
}
c.err = err
c.cause = cause
//关掉ctx中的管道
d, _ := c.done.Load().(chan struct{})
if d == nil {
c.done.Store(closedchan)
} else {
close(d)
}
//遍历ctx的子ctx,一个一个撤销,最终该分支下的全被撤销掉
for child := range c.children {
child.cancel(false, err, cause)
}
c.children = nil
c.mu.Unlock()
//是否要从父ctx移除该ctx,假如传入的是就移除
if removeFromParent {
removeChild(c.Context, c)
}
}
timerCtx
重写了该办法,首要经过调用父cancelCtx的cancel办法并删掉timer
func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {
c.cancelCtx.cancel(false, err, cause)
if removeFromParent {
removeChild(c.cancelCtx.Context, c)
}
//它的锁是用的父cancelCtx的锁
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}
type timerCtx struct {
*cancelCtx
timer *time.Timer
deadline time.Time
}
创立带有过期时刻的Ctx
传入一个Ctx和时限,回来一个Ctx和撤销它的函数
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
//假如没有传入parent,则报错
if parent == nil {
panic("cannot create context from nil parent")
}
//获取parent过期时刻,若获取成功且此刻刻在设定的时刻之前,那么就听parent的话,与其一起过期
//调用WithCancel,此函数会回来一个cancelCtx和撤销函数
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
return WithCancel(parent)
}
//假如没有获取到parent过期时刻或许获取到的时刻现已过了设定时刻
//就创立一个timerCtx,赋予过期时刻为设定的时刻
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
//创立完后要填到parent的children里
propagateCancel(parent, c)
dur := time.Until(d)
//假如现已超时,就cancel掉此ctx并从它的parent的children里移除
//再回来该ctx(???不了解这点,拿这个剥离出来的cancel掉的ctx干啥)
if dur <= 0 {
c.cancel(true, DeadlineExceeded, nil)
return c, func() { c.cancel(false, Canceled, nil) }
}
//锁住这个ctx
c.mu.Lock()
defer c.mu.Unlock()
//假如该ctx还没被cancel就等到设定时刻调用cancel
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded, nil)
})
}
return c, func() { c.cancel(true, Canceled, nil) }
}
//此函数便是WithDeadline的一个补充函数,它传入的是时刻段,WithDeadline传入的是时刻点,作用相同
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
ValueCtx相关
ValueCtx只担任带着Key-Value键值对,其他交给父Ctx做
type valueCtx struct {
Context
key, val any
}
创立value
没啥好说的
func WithValue(parent Context, key, val any) Context {
if parent == nil {
panic("cannot create context from nil parent")
}
if key == nil {
panic("nil key")
}
//查看这个key能不能作比较,假如不能就不能拿它当key
//为什么呢,由于假如key不能比较,咱们就无法经过查找key来拿到对应的value
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}
获取value
func (c *valueCtx) Value(key any) any {
//能直接拿到就拿
if c.key == key {
return c.val
}
//不能就往上找
return value(c.Context, key)
}
value函数
这个就像一个办法合集,经过对传入的ctx类型判别来调用相应的办法,假如在当前ctx无法取到值就会一向往上找
func value(c Context, key any) any {
for {
switch ctx := c.(type) {
case *valueCtx:
if key == ctx.key {
return ctx.val
}
c = ctx.Context
case *cancelCtx:
if key == &cancelCtxKey {
return c
}
c = ctx.Context
case *timerCtx:
if key == &cancelCtxKey {
return ctx.cancelCtx
}
c = ctx.Context
case *emptyCtx:
return nil
default:
return c.Value(key)
}
}
}
总结
这是我第一次阅览源码,尽管context包很简略,可是我读起来真的好吃力
读着读着总会惊叹写这些代码的人脑子是怎样长的?vocal为什么能写的那么高雅,有些奇思妙想真的好牛