Context应用

package main

import (
    "context"
    "fmt"
    "math/rand"
    "os"
    "os/signal"
    "sync"
    "syscall"
    "time"
)

func doFunc1(ctx context.Context, prompt string, wg *sync.WaitGroup) {
    defer func() {
        <-time.After(time.Duration(rand.Intn(5000)) * time.Millisecond)
        fmt.Printf("%v is leaving.\n", prompt)
        wg.Done()
    }()

    if "uno" == prompt {
        wg.Add(1)
        newCtx, _ := context.WithCancel(ctx)
        go doFunc1(newCtx, "tres", wg)
    }

    intv := time.NewTicker(time.Second + time.Duration(rand.Intn(1000))*time.Millisecond)
    for {
        select {
        case <-intv.C:
            fmt.Printf("<%v>tick tick\n", prompt)
        case <-ctx.Done():
            return
        default:
        }
    }
}

func main() {
    rand.Seed(time.Now().UnixNano())
    defer fmt.Println("bye")
    ctx, doCancel := context.WithCancel(context.Background())
    var wg sync.WaitGroup
    wg.Add(2)
    go doFunc1(ctx, "uno", &wg)
    go doFunc1(ctx, "dos", &wg)

    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, os.Kill, os.Interrupt, syscall.SIGTERM)

    <-sigCh
    fmt.Println("closing")
    doCancel()

    timeoutCtx, _ := context.WithTimeout(context.Background(), 3*time.Second)
    doneCh := make(chan struct{}, 1)
    go func() {
        defer fmt.Println("waited 2000.")
        wg.Wait()
        close(doneCh)
    }()
    select {
    case <-doneCh:
        fmt.Printf("waited done.\n")
    case <-timeoutCtx.Done():
        fmt.Printf("done error: %v\n", timeoutCtx.Err())
    }

}
  1. 带“嵌套”的go呼叫. context的cancel行为是带传递性的transitive
  2. 就算有呼叫者的主动cancel, 也需要子过程主动去查询“自己是否被cancel掉”
  3. 如果使用sync.WaitGroup的Wait, 可能存在永远的等待.

源码分析: (1.9.2)
Done()完成之后Err()的返回值属于以下两个:

// Canceled is the error returned by Context.Err when the context is canceled.
var Canceled = errors.New("context canceled")

// DeadlineExceeded is the error returned by Context.Err when the context's
// deadline passes.
var DeadlineExceeded error = deadlineExceededError{}

type deadlineExceededError struct{}

注: error在golang中是一个接口:仅需要实现Error()string即可.


// propagateCancel arranges for child to be canceled when parent is.
func propagateCancel(parent Context, child canceler) {
    if parent.Done() == nil {
        return // parent is never canceled
    }
    if p, ok := parentCancelCtx(parent); ok {
        p.mu.Lock()
        if p.err != nil {
            // parent has already been canceled
            child.cancel(false, p.err)
        } else {
            if p.children == nil {
                p.children = make(map[canceler]struct{})
            }
            p.children[child] = struct{}{}
        }
        p.mu.Unlock()
    } else {
        go func() {
            select {
            case <-parent.Done():
                child.cancel(false, parent.Err())
            case <-child.Done():
            }
        }()
    }
}

建立context对象初期就会去创建一个goroutine, 用select挂在parent和自己的done channel上。

// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
    cancel(removeFromParent bool, err error)
    Done() <-chan struct{}
}

WithCancel除了创建对象也要返回canceler, canceler就是对 cancelContext对象上调用cancel方法:
cancel之后总是会标记error, 所以canceler可以重复调用。

原文链接:
https://siadat.github.io/post/context

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值