go context

Created

2024-10-28 13:28:54

Updated

2024-10-28 13:29:00

Diagram

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(): // 关心自己
            }
        }()
    }
}
for child := range c.children {
    child.cancel(false, err, cause)
}
// 如果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
}
func (c *valueCtx) Value(key any) any {
    if c.key == key {
        return c.val
    }
    return value(c.Context, key)
}
func (c *cancelCtx) Value(key any) any {
    if key == &cancelCtxKey {
        return c
    }
    return value(c.Context, key)
}

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)
}
func TestWithDeadline(t *testing.T) {
    ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(3*time.Second))
    defer cancel()
    fmt.Println(ctx)
}
Back to top