asp网站配色,微信自媒体网站建设,随州建设网站,向网站服务器上传网页文件下载文章目录 1. 简介2. 常见用法2.1 控制goroutine的生命周期#xff08;cancel#xff09;2.2 传递超时#xff08;Timeout#xff09;信息2.3 传递截止时间#xff08;Deadline#xff09;2.4 传递请求范围内的全局数据 #xff08;value#xff09; 3 特点3.1 上下文的… 文章目录 1. 简介2. 常见用法2.1 控制goroutine的生命周期cancel2.2 传递超时Timeout信息2.3 传递截止时间Deadline2.4 传递请求范围内的全局数据 value 3 特点3.1 上下文的不可变性3.2 链式传递3.3 超时和截止时间3.4 多级取消机制3.5 context.Value() 传递全局数据3.6 context 线程安全3.7 层次化的取消信号传播 4 底层实现原理4.1 Context接口4.2 emptyCtx类4.3 cancelCtx类4.4 timerCtx类4.5 valueCtx类 1. 简介
在 Go 语言中context 包主要用于在 并发编程 中控制和管理 goroutine 的生命周期。它提供了一种机制可以通过传递 context.Context 来协调多个 goroutine特别是在需要取消操作、超时控制和传递共享数据时。
2. 常见用法 2.1 控制goroutine的生命周期cancel
context 允许父 goroutine 可以通知子 goroutine 停止工作。例如当你在 HTTP 服务器中处理一个请求时如果客户端关闭了连接你可能不再需要继续处理这个请求。通过 context你可以在主流程中取消子流程即 goroutine避免不必要的资源消耗。
ctx, cancel : context.WithCancel(context.Background())
go func() {select {case -ctx.Done():// 接收到取消信号停止工作fmt.Println(Goroutine canceled)}
}()
// 取消操作
cancel()2.2 传递超时Timeout信息
context 还可以传递一个超时时间允许操作在指定时间后自动停止。这对于需要限定时间完成的操作如数据库查询、API 调用非常有用。
ctx, cancel : context.WithTimeout(context.Background(), time.Second*2)
defer cancel()select {
case -time.After(time.Second * 1):fmt.Println(Operation completed within timeout)
case -ctx.Done():fmt.Println(Operation timed out)
}
2.3 传递截止时间Deadline
context 可以包含一个截止时间Deadline这是在某个具体的时间点之后所有的操作都应该停止。与超时类似但它使用绝对时间而不是相对时间 。
deadline : time.Now().Add(time.Second * 5)
ctx, cancel : context.WithDeadline(context.Background(), deadline)
defer cancel()select {
case -time.After(time.Second * 6):fmt.Println(Operation completed)
case -ctx.Done():fmt.Println(Deadline exceeded)
}2.4 传递请求范围内的全局数据 value
context 还可以携带一些全局数据虽然 context 设计的初衷并不是为了数据传递但在一些场景下利用 context 传递与请求上下文相关的信息是很常见的做法。例如在 Web 服务中传递用户认证信息、跟踪请求 ID 等。
package mainimport (contextfmt
)// 创建一个 key 类型用于在 context 中存储和检索数据
type key stringconst userIDKey key userID// 传递用户 ID 的函数
func processRequest(ctx context.Context) {// 从 context 中取出用户 IDuserID : ctx.Value(userIDKey)if userID ! nil {fmt.Println(User ID found in context:, userID)} else {fmt.Println(No User ID found in context)}
}func main() {// 创建一个带有用户 ID 的 contextctx : context.WithValue(context.Background(), userIDKey, 12345)// 传递带有用户 ID 的 contextprocessRequest(ctx)// 调用时没有用户 IDprocessRequest(context.Background())
}
3 特点
3.1 上下文的不可变性
context 是不可变的。一旦创建了一个 context你不能修改它。每次你想要添加新的值、超时或取消机制时都会创建一个新的子 context。这有助于保持 context 的并发安全确保多个 goroutine 可以共享同一个 context 而不发生数据竞争。
例如使用 context.WithValue 添加键值对时实际上是创建了一个新的 context旧的 context 保持不变。
parentCtx : context.Background()
childCtx : context.WithValue(parentCtx, key, value)3.2 链式传递
context 是链式传递的。你可以从一个父 context 创建多个子 context并且这些子 context 可以继续创建它们的子 context。这种设计允许你在复杂的程序中管理多个 context并且可以确保每个子 context 都能够追溯到其父 context。
例如当你调用 context.WithCancel 或 context.WithTimeout 时都会基于父 context 创建新的子 context。
3.3 超时和截止时间
context 允许你为操作设置 超时时间 或 截止时间。通过 context.WithTimeout 和 context.WithDeadline你可以确保某些操作在指定时间内完成否则自动取消。这对于网络请求、数据库查询等可能需要限制执行时间的操作非常有用。
context.WithTimeout为上下文设置相对的超时时间。例如在 5 秒后自动取消。
ctx, cancel : context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()context.WithDeadline为上下文设置绝对的截止时间。例如在某个具体的时间点后取消。
deadline : time.Now().Add(10 * time.Second)
ctx, cancel : context.WithDeadline(context.Background(), deadline)
defer cancel()在这两种情况下一旦时间到了ctx.Done() 通道会关闭所有使用这个 context 的操作都会被取消。
3.4 多级取消机制
除了父 context 取消时会取消所有子 context 外子 context 也可以通过独立的取消机制取消自己。例如如果你为某个子 context 设置了独立的超时或调用了它的 cancel 函数子 context 会取消而其他的兄弟 context 或父 context 不受影响。
parentCtx, parentCancel : context.WithCancel(context.Background())
childCtx, childCancel : context.WithTimeout(parentCtx, 2*time.Second)go func() {-childCtx.Done()fmt.Println(Child context cancelled)
}()time.Sleep(3 * time.Second)
parentCancel() // 取消父 context在这个例子中childCtx 会因为超时先被取消而 parentCtx 只有在调用 parentCancel() 后才会取消。
3.5 context.Value() 传递全局数据
虽然 context 的设计主要是用于控制 goroutine 的生命周期但它也可以通过 context.WithValue 传递少量的全局数据键值对。常见的使用场景包括在请求链路中传递用户认证信息、请求 ID 等。
注意
context.Value() 传递的数据应该是与请求相关的少量数据而不应当被滥用为数据存储机制。使用自定义的键类型如 type key string可以避免键冲突。
type key string
ctx : context.WithValue(context.Background(), key(userID), 12345)
userID : ctx.Value(key(userID))
fmt.Println(User ID:, userID)3.6 context 线程安全
context 是设计为并发安全的。你可以将同一个 context 实例传递给多个 goroutine而不需要担心数据竞争或同步问题。因为 context 的状态如取消、超时通过不可变的方式和通道传播它天然支持并发。
func worker(ctx context.Context, id int) {select {case -time.After(3 * time.Second):fmt.Printf(Worker %d done\n, id)case -ctx.Done():fmt.Printf(Worker %d cancelled\n, id)}
}func main() {ctx, cancel : context.WithCancel(context.Background())for i : 1; i 3; i {go worker(ctx, i)}time.Sleep(1 * time.Second)cancel() // 取消所有子 goroutinetime.Sleep(4 * time.Second)
}3.7 层次化的取消信号传播
在 context 结构中当父 context 被取消时所有子 context 会自动取消这是一种层次化的取消信号传播机制。这可以帮助你在复杂系统中控制多个 goroutine 的生命周期。例如在 Web 服务中当用户取消请求时所有与该请求相关的操作都应该立即停止。
在 context 被取消时context.Err() 会返回具体的错误类型
context.Canceled表示上下文被显式取消例如调用了 cancel()。context.DeadlineExceeded表示上下文超时或截止时间已过。
4 底层实现原理
主要通过解析context的源码来剖析第三节中各个特点的实现机理。
4.1 Context接口
type Context interface {Deadline() (deadline time.Time, ok bool)Done() -chan struct{}Err() errorValue(key any) any
}这是context最核心的一个接口定义了Context接口包含了四个方法其他结构体只要实现了这四个方法就实现了该接口。四个方法的功能分别是
Deadline() (deadline time.Time, ok bool)Deadline 方法返回一个时间time.Time表示在这个上下文下执行的任务应该取消的截止时间。通常用在需要在某个时间点前完成的任务中。如果 context 没有设置任何截止时间Deadline() 的返回值中ok 会是 false。这意味着当前 context 没有时间限制因此任务可以无限期地运行除非被手动取消。对 Deadline() 的连续调用会返回相同的结果。Done() -chan struct{}Done() 方法返回一个通道用于通知 goroutine 某个 context 关联的任务应该被取消。Done() 返回一个通道chan struct{}这个通道在 context 关联的工作需要被取消时会关闭。你可以通过监听这个通道来决定是否继续执行某个操作。Err() errorErr() 方法用于返回 context 的取消原因尤其在 Done() 通道关闭之后它提供了上下文取消的具体原因。如果 Done() 通道还没有关闭即 context 没有被取消或超时调用 Err() 方法将返回 nil。如果 Done() 通道已经关闭Err() 方法会返回一个非空的错误error这个错误解释了为什么 context 被取消。1 如果 context 是由于调用 cancel() 函数而被取消的Err() 将返回一个 context.Canceled 错误。这表明操作是手动取消的。 2 如果 context 是因为超时或截止时间到达而被取消的Err() 将返回一个 context.DeadlineExceeded 错误。这表明操作是因为超时被取消的。Value(key any) anyValue() 方法根据传入的键key返回与该键相关联的值。如果该键没有与任何值相关联则返回 nil。 键的类型可以是支持相等性判断的任何类型通常是简单的标识符类型。为了避免不同包之间的键冲突应该将键定义为未导出类型即首字母小写的类型。
4.2 emptyCtx类
emptyCtx 是一种特殊的上下文它永远不会被取消、没有任何关联的值也没有截止时间。它是 context.Background() 和 context.TODO() 的基础类型。
type emptyCtx struct{}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
}context.Background() 与 context.TODO()
func Background() Context {return backgroundCtx{}
}func TODO() Context {return todoCtx{}
}type backgroundCtx struct{ emptyCtx }func (backgroundCtx) String() string {return context.Background
}type todoCtx struct{ emptyCtx }func (todoCtx) String() string {return context.TODO
}context.Background()这是 Go 程序中通常用作根上下文的上下文。它一般用于顶层的 context例如在启动 goroutine、处理请求、初始化程序时使用。由于它基于 emptyCtx所以它不会被取消、没有值或截止时间。
ctx : context.Background()context.TODO()TODO() 通常用于代码还在编写过程中开发者不确定应该使用哪种 context 的场景下。它是一个临时的占位符表示将来会替换为合适的上下文。和 Background() 一样TODO() 也是基于 emptyCtx没有取消、值或截止时间。
ctx : context.TODO()4.3 cancelCtx类
type cancelCtx struct {Contextmu sync.Mutex // protects following fieldsdone atomic.Value // of chan struct{}, created lazily, closed by first cancel callchildren map[canceler]struct{} // set to nil by the first cancel callerr error // set to non-nil by the first cancel callcause error // set to non-nil by the first cancel call
}// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {cancel(removeFromParent bool, err, cause error)Done() -chan struct{}
}cancelCtx 是可以被取消的当 cancelCtx 被取消时它会自动取消所有实现了 canceler 接口的子上下文。子上下文需要实现 canceler 接口canceler 接口通常会有一个 cancel() 方法用于执行取消操作。一旦父上下文的 cancel() 被调用父上下文会遍历所有子上下文调用它们的 cancel() 方法将取消信号逐层向下传递。
func (c *cancelCtx) Value(key any) any {if key cancelCtxKey {return c}return value(c.Context, key)
}func (c *cancelCtx) Done() -chan struct{} {d : c.done.Load()if d ! nil {return d.(chan struct{})}c.mu.Lock()defer c.mu.Unlock()d c.done.Load()if d nil {d make(chan struct{})c.done.Store(d)}return d.(chan struct{})
}func (c *cancelCtx) Err() error {c.mu.Lock()err : c.errc.mu.Unlock()return err
}func (c *cancelCtx) Value(key any) any如果 key 与特定的 cancelCtxKey 相等则返回当前 cancelCtx 实例本身否则调用嵌套的父上下文的 Value 方法查找值。func (c *cancelCtx) Done() -chan struct{}第一次调用 Done()如果 done 通道还没有创建程序会在加锁的情况下懒加载创建这个通道。done 通道被存储在 c.done 中确保后续的 Done() 调用直接返回这个通道。后续调用 Done()后续调用 Done() 时会直接通过 c.done.Load() 读取已经创建的 done 通道避免不必要的锁操作提高了性能。func (c *cancelCtx) Err() error在锁保护下获取err
func withCancel(parent Context) *cancelCtx {if parent nil {panic(cannot create context from nil parent)}c : cancelCtx{}c.propagateCancel(parent, c)return c
}func (c *cancelCtx) propagateCancel(parent Context, child canceler) {c.Context parentdone : parent.Done()if done nil {return // parent is never canceled}select {case -done:// parent is already canceledchild.cancel(false, parent.Err(), Cause(parent))returndefault:}if p, ok : parentCancelCtx(parent); ok {// parent is a *cancelCtx, or derives from one.p.mu.Lock()if p.err ! nil {// parent has already been canceledchild.cancel(false, p.err, p.cause)} else {if p.children nil {p.children make(map[canceler]struct{})}p.children[child] struct{}{}}p.mu.Unlock()return}if a, ok : parent.(afterFuncer); ok {// parent implements an AfterFunc method.c.mu.Lock()stop : a.AfterFunc(func() {child.cancel(false, parent.Err(), Cause(parent))})c.Context stopCtx{Context: parent,stop: stop,}c.mu.Unlock()return}goroutines.Add(1)go func() {select {case -parent.Done():child.cancel(false, parent.Err(), Cause(parent))case -child.Done():}}()
}// parentCancelCtx(parent) 是一个辅助函数检查父上下文是否是 cancelCtx 类型
// 或者是否派生自 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
}propagateCancel实现了 cancelCtx 中的 propagateCancel 方法它的作用是将取消信号从父上下文parent传播到子上下文child并根据不同的情况处理取消逻辑。该方法负责建立父子上下文之间的取消关系当父上下文被取消时子上下文也会自动取消。这是 Go 语言 context 机制中取消信号传播的核心部分。
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {if err nil {panic(context: internal error: missing cancel error)}if cause nil {cause err}c.mu.Lock()if c.err ! nil {c.mu.Unlock()return // already canceled}c.err errc.cause caused, _ : c.done.Load().(chan struct{})if d nil {c.done.Store(closedchan)} else {close(d)}for child : range c.children {// NOTE: acquiring the childs lock while holding parents lock.child.cancel(false, err, cause)}c.children nilc.mu.Unlock()if removeFromParent {removeChild(c.Context, c)}
}cancel() 方法用于取消当前上下文并传播取消信号给它的子上下文。
func removeChild(parent Context, child canceler) {if s, ok : parent.(stopCtx); ok {s.stop()return}p, ok : parentCancelCtx(parent)if !ok {return}p.mu.Lock()if p.children ! nil {delete(p.children, child)}p.mu.Unlock()
}removeChild将子上下文从父上下文中移除。
4.4 timerCtx类
type timerCtx struct {cancelCtxtimer *time.Timer deadline time.Time
}timerCtx 是 context 的一种实现它用于处理超时timeout或截止时间deadline。通过嵌入 cancelCtxtimerCtx 实现了取消上下文的能力并通过计时器控制上下文的超时行为。
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) {return WithDeadlineCause(parent, d, nil)
}func WithDeadlineCause(parent Context, d time.Time, cause error) (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{deadline: d,}c.cancelCtx.propagateCancel(parent, c)dur : time.Until(d)if dur 0 {c.cancel(true, DeadlineExceeded, cause) // deadline has already passedreturn 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, cause)})}return c, func() { c.cancel(true, Canceled, nil) }
}WithTimeou 和 WithDeadline用于创建带有超时或截止时间的上下文。超时后自动取消上下文返回 context.DeadlineExceeded 错误。WithDeadlineCause核心逻辑处理创建上下文并设置定时器定时器到期时自动取消上下文。它还支持设置取消原因cause。取消函数 CancelFunc无论上下文是否超时调用 CancelFunc 都可以立即取消上下文。
func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {if err nil {panic(context: internal error: missing cancel error)}if cause nil {cause err}c.mu.Lock()if c.err ! nil {c.mu.Unlock()return // already canceled}c.err errc.cause caused, _ : c.done.Load().(chan struct{})if d nil {c.done.Store(closedchan)} else {close(d)}for child : range c.children {// NOTE: acquiring the childs lock while holding parents lock.child.cancel(false, err, cause)}c.children nilc.mu.Unlock()if removeFromParent {removeChild(c.Context, c)}
}它会关闭与取消相关的通道取消所有子上下文必要时将当前上下文从父上下文中移除并且在第一次取消时设置取消原因。
4.5 valueCtx类
type valueCtx struct {Contextkey, val any
}valueCtx 同样继承了一个 parent context一个 valueCtx 中仅有一组 kv 对.
func WithValue(parent Context, key, val any) Context {if parent nil {panic(cannot create context from nil parent)}if key nil {panic(nil key)}if !reflectlite.TypeOf(key).Comparable() {panic(key is not comparable)}return valueCtx{parent, key, val}
}倘若 parent context 为空panic倘若 key 为空 panic倘若 key 的类型不可比较panic包括 parent context 以及 kv对返回一个新的 valueCtx.
func (c *valueCtx) Value(key any) any {if c.key key {return c.val}return value(c.Context, key)
}func value(c Context, key any) any {for {switch ctx : c.(type) {case *valueCtx:if key ctx.key {return ctx.val}c ctx.Contextcase *cancelCtx:if key cancelCtxKey {return c}c ctx.Contextcase withoutCancelCtx:if key cancelCtxKey {// This implements Cause(ctx) nil// when ctx is created using WithoutCancel.return nil}c ctx.ccase *timerCtx:if key cancelCtxKey {return ctx.cancelCtx}c ctx.Contextcase backgroundCtx, todoCtx:return nildefault:return c.Value(key)}}
}假如当前 valueCtx 的 key 等于用户传入的 key则直接返回其 value假如不等则从 parent context 中依次向上寻找.