go context
1 Background & TODO
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
// 作为初始的ctx,是起点时使用这个来实例化,然后传递给后续的
func Background() Context {
return background
}
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
// 不是起点时,然后你暂时不知道使用哪个context,代码里就暂时写上,
// 把TODO作为方法 可以说其实就表明了之后逻辑弄完后,你应该再来看看具体是使用什么
func TODO() Context {
return todo
}2 WithValue
package test
import (
"context"
"fmt"
"testing"
"time"
)
func TestWithValue(t *testing.T) {
ctx := context.Background()
parent := context.WithValue(ctx, "name", "tom")
son := context.WithValue(parent, "age", "11")
fmt.Println(parent.Value("name"))
fmt.Println(parent.Value("age")) // nil 无法获取子ctx 设置的key
fmt.Println(son.Value("name"))
}
// 我们一般是不会这么做的
func TestWithValueMap(t *testing.T) {
ctx := context.Background()
parent := context.WithValue(ctx, "m", map[string]string{"name": "tom"})
m := parent.Value("m").(map[string]string)
m["age"] = "11"
son := context.WithValue(parent, "m", m)
fmt.Println(parent.Value("m").(map[string]string)["name"])
// 父 ctx 获取 子 ctx 设置的key
fmt.Println(parent.Value("m").(map[string]string)["age"])
fmt.Println(son.Value("m").(map[string]string)["name"])
}3 WithCancel
Tip
父ctx cancel或超时, 所有子孙ctx都会cancel或超时
package test
import (
"context"
"fmt"
"testing"
"time"
)
// 前面timeout 可以说是到期会自动cancel
// 这里是需要我们主动 cancel ,用来控制链路流程,比如控制子goroutine 退出
func TestWithCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
go func(context.Context) {
go func(context.Context) {
c, cancelSon := context.WithCancel(ctx)
defer cancelSon()
Loop:
for {
select {
case <-c.Done(): // 父ctx cancel 会导致子ctx 也cancel
fmt.Println("gorouting2 is done")
// context canceled ,很明星, cancelSon() 是没执行的. 但是err 信息是
fmt.Println(c.Err())
break Loop
default:
}
fmt.Println("gorouting2 is running")
time.Sleep(time.Second)
}
}(ctx)
Loop:
for {
select {
case <-ctx.Done():
fmt.Println("gorouting1 is done")
break Loop
default:
}
fmt.Println("gorouting1 is running")
time.Sleep(time.Second)
}
}(ctx)
time.Sleep(3 * time.Second)
// 主动取消
cancel()
fmt.Println("cancel...")
time.Sleep(3 * time.Second)
}// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done atomic.Value // of chan struct{}, created lazily, closed by first cancel call
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
}
func withCancel(parent Context) *cancelCtx {
if parent == nil {
panic("cannot create context from nil parent")
}
c := newCancelCtx(parent)
propagateCancel(parent, c)
return c
}
func newCancelCtx(parent Context) *cancelCtx {
return &cancelCtx{Context: parent}
}
// 父 cancel后, 将cancel 传递给子孙 (子孙也cancel)
func propagateCancel(parent Context, child canceler) {
done := parent.Done()
if done == nil {
// WithValue 的done() 是nil
return
}
select {
case <-done:
// 如果这个时候父 ctx cancel 了
// 则调用子孙的cancel, 将 父ctx的Err,以及谁引发的传递
child.cancel(false, parent.Err(), Cause(parent))
return
default: // 因为有default 所以如果 <-done 没有数据,就会立马结束,走后面的代码
}
if p, ok := parentCancelCtx(parent); ok {
// 如果找到最近一个是cancelCtx 的祖先context
p.mu.Lock()
if p.err != nil {
// 再次判断
// 如果父ctx 这个时候cancel后, 那么直接cancel 子ctx
child.cancel(false, p.err, p.cause)
} else {
if p.children == nil {
p.children = make(map[canceler]struct{})
}
// 设置children ,子 ctx, 内容是 空结构体省内存
// 后面我们只需要key ctx 就行
// 后续 父cancel时,需要cancel 所有children 用到
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(): // 关心自己
}
}()
}
}// 如果parent是cancelCtx,则返回parent ctx的指针
// 如果不是cancelCtx (比如WithValue 返回的ctx),则继续往前找,找到最近的祖先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
}4 WithDeadline/Timeout
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
type timerCtx struct {
*cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
// WithTimeout 里面直接调用了 WithDeadline , 和它没啥区别
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
// The current deadline is already sooner than the new one.
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded, nil) // deadline has already passed
return c, func() { c.cancel(false, Canceled, nil) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded, nil)
})
}
return c, func() { c.cancel(true, Canceled, nil) }
}package test
import (
"context"
"fmt"
"testing"
"time"
)
func TestWithTimeout(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
dl, isSetDl := ctx.Deadline()
fmt.Printf("预计的过期时间: %s \n是否设置了过期时间:%t\n", dl, isSetDl) // 预计的过期时间
_, isSetDl2 := context.Background().Deadline()
fmt.Println(isSetDl2) // false
now := time.Now()
go func() {
// 到期后, Done() 管道会有数据
<-ctx.Done()
// 到期的情况时 错误信息
fmt.Println("err", ctx.Err()) // err context deadline exceeded
fmt.Println("经过", time.Since(now), time.Now())
}()
time.Sleep(4 * time.Second)
}
func TestWithTimeout2(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
// 超时 设置无效的,父ctx超时了, 子孙也超时了.
s, c := context.WithTimeout(ctx, 5*time.Second)
defer c()
go func() {
<-s.Done()
fmt.Println("s:", s.Err())
}()
go func() {
// 到期后, Done() 管道会有数据
<-ctx.Done()
// 到期的情况时 错误信息
fmt.Println("err", ctx.Err()) // err context deadline exceeded
}()
time.Sleep(7 * time.Second)
}
func TestWithTimeoutCancel(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
now := time.Now()
go func() {
// 主动执行cancel()后, Done() 管道会有数据
<-ctx.Done()
err := ctx.Err()
fmt.Println("err", err) // err context canceled
switch err {
// 判断错误类型
case context.Canceled:
case context.DeadlineExceeded:
}
fmt.Println("经过", time.Since(now))
}()
time.Sleep(1 * time.Second)
cancel()
time.Sleep(3 * time.Second)
}