Golang的context

目录

context的基本使用

为什么需要context

Context interface 

 标准 error

emptyCtx 

cancelCtx 

Deadline 方法

Done 方法

Err 方法

Value 方法

context.WithCancel()

newCancelCtx

WithCancel中propagateCancel

cancel

timerCtx

valueCtx  


context的基本使用

创建:

context.Background():创建一个空的父类context

context.TODO(): 创建一个未来 可能有内容的父类 context,有扩展性

	// 创建一个可以取消的Context
	ctxCancel, cancelCancel := context.WithCancel(context.Background())
    defer cancelCancel() // 当不再需要时,确保调用cancel来释放资源

    // 创建一个带有截止时间的Context
    deadline := time.Now().Add(time.Second * 10)
 	ctxDeadline, cancelDeadline := context.WithDeadline(context.Background(), deadline)
 	defer cancelDeadline()


    // 创建一个带有超时时间的Context
 	timeout := 10 * time.Second
 	ctxTimeout, cancelTimeout := context.WithTimeout(context.Background(), timeout)
 	defer cancelTimeout()

返回值: Context 上下文本体 和 CancelFunc 关闭Context 的函数

类别:

context.WithCancel 创建一个可以取消的Context,当调用 返回值 cancel 时 就可以通知关闭

context.WithDeadline 创建一个带有截止时间的Context,到一个特定时间时便发出通知

context.WithTimeout 创建一个带有超时时间的Context,过了一段时间后就发出通知

防止go协程泄漏使用例子

package main

import (
	"context"
	"fmt"
)

func main() {
	//WithCancel(ctx Context, cancel CancelFunc)=(名 Context,处理函数 CancelFunc)
	ctx, cancel := context.WithCancel(context.Background()) //context.Background() 处理 Goroutine
	context.TODO()
	ch := func(ctx context.Context) <-chan int {
		ch := make(chan int)
		go func() {
			for i := 0; ; i++ {
				select {
				case <-ctx.Done():
					return
				case ch <- i:
				}
			}
		}()
		return ch
	}(ctx)

	for v := range ch {
		fmt.Println(v)
		if v == 5 {
			cancel()
			break
		}
	}
}

为什么需要context

场景1: 当主协程启动了m个子协程,m个子协程又启动更多的协程,

那监控起来需要很多的channel, 操作非常繁琐。

如果我们使用 context 时,当父类的context关闭,子类也会一起关闭以此类推,类似Qt中的对象树

场景2:任务A 挂在 任务B 下,我们希望 B 有着定时退出的功能,而且当B退出时A也需要退出

使用定时器+channel时,就显的有些繁琐,我们可以直接使用context.WithTimeout 一步到位

Context interface 

golang的接口 类似 c++的基类

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key any) any
}

Context 为 interface,定义了四个核心 api:

• Deadline:返回 context 的过期时间;

• Done:返回 context 中的 channel;

• Err:返回错误;

• Value:返回 context 中的对应 key 的值.

 标准 error

var Canceled = errors.New("context canceled")

var DeadlineExceeded error = deadlineExceededError{}

type deadlineExceededError struct{}

func (deadlineExceededError) Error() string   { return "context deadline exceeded" }
func (deadlineExceededError) Timeout() bool   { return true }
func (deadlineExceededError) Temporary() bool { return true

• Canceled:context 被 cancel 时会报此错误;

• DeadlineExceeded:context 超时时会报此错误.

emptyCtx 

重写了接口 api:

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 
}

返回的什么东西都为空 

• emptyCtx 是一个空的 context,本质上类型为一个整型;

• Deadline 方法会返回一个公元元年时间以及 false 的 flag,标识当前 context 不存在过期时间;

• Done 方法返回一个 nil 值,用户无论往 nil 中写入或者读取数据,均会陷入阻塞;

• Err 方法返回的错误永远为 nil;

• Value 方法返回的 value 同样永远为 nil.

var (
    background = new(emptyCtx)
    todo       = new(emptyCtx)
)

func Background() Context {
    return background
}

func TODO() Context {
    return todo
}

 当使用context.Background() & context.TODO() 创建emptyCtx 时返回固定的 emptyCtx 

cancelCtx 

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
}

type canceler interface {
    cancel(removeFromParent bool, err error)
    Done() <-chan struct{}
}

Deadline 方法

cancelCtx 未实现该方法,仅是 embed 了一个带有 Deadline 方法的 Context interface,因此倘若直接调用会报错.


Done 方法

流程如下:

源码:

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{})
}


• 基于 atomic 包,读取 cancelCtx 中的 chan;倘若已存在,则直接返回;

• 加锁后,在此检查 chan 是否存在,若存在则返回;(双重检查 double check)

• 初始化 chan 存储到 aotmic.Value 当中,并返回.(懒加载机制 懒汉)

 

Err 方法

func (c *cancelCtx) Err() error {
    c.mu.Lock()
    err := c.err
    c.mu.Unlock()
    return err
}

• 加锁;

• 读取 cancelCtx.err;

• 解锁;

• 返回结果;

Value 方法

func (c *cancelCtx) Value(key any) any {
    if key == &cancelCtxKey {
        return c
    }
    return value(c.Context, key)
}

• 倘若 key 特定值 &cancelCtxKey,则返回 cancelCtx 自身的指针;

• 否则遵循 valueCtx 的思路取值返回;

context.WithCancel()

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
    if parent == nil {
        panic("cannot create context from nil parent")
    }
    c := newCancelCtx(parent)
    propagateCancel(parent, &c)
    return &c, func() { c.cancel(true, Canceled) }
}

• 校验父 context 非空;

• 注入父 context 构造好一个新的 cancelCtx;

• 在 propagateCancel 方法内启动一个守护协程,以保证父 context 终止时,该 cancelCtx 也会被终止;

• 将 cancelCtx 返回,连带返回一个用以终止该 cancelCtx 的闭包函数.

newCancelCtx

没用propagateCancel 方法

func newCancelCtx(parent Context) cancelCtx {
    return cancelCtx{Context: parent}
}

• 注入父 context 后,返回一个新的 cancelCtx.

WithCancel中propagateCancel

propagateCancel 传播取消

func propagateCancel(parent Context, child canceler) {
    done := parent.Done()
    if done == nil {
        return // parent is never canceled
    }

    select {
    case <-done:
        // parent is already canceled
        child.cancel(false, parent.Err())
        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)
        } else {
            if p.children == nil {
                p.children = make(map[canceler]struct{})
            }
            p.children[child] = struct{}{}
        }
        p.mu.Unlock()
    } else {
        atomic.AddInt32(&goroutines, +1)
        go func() {
            select {
            case <-parent.Done():
                child.cancel(false, parent.Err())
            case <-child.Done():
            }
        }()
    }
}

cancel

// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's children.
// cancel sets c.cause to cause if this is the first time c is canceled.
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 = err
	c.cause = cause
	d, _ := c.done.Load().(chan struct{})
	if d == nil {
		c.done.Store(closedchan)
	} else {
		close(d)
	}
	for child := range c.children {
		// NOTE: acquiring the child's lock while holding parent's lock.
		child.cancel(false, err, cause)
	}
	c.children = nil
	c.mu.Unlock()

	if removeFromParent {
		removeChild(c.Context, c)
	}
}

timerCtx

只介绍类,其他与cancel差不多,加上了过期时刻


type timerCtx struct {
    cancelCtx
    timer *time.Timer // Under cancelCtx.mu.

    deadline time.Time
}

valueCtx  

键值对 Ctx

type valueCtx struct {
    Context
    key, val any
}

参考 : 小徐先生1212 --context 底层实现 

相关推荐

  1. golang context

    2024-06-14 21:40:02       41 阅读
  2. Golang 如何基于现有 context 创建新 context

    2024-06-14 21:40:02       50 阅读
  3. golangcontext详解

    2024-06-14 21:40:02       57 阅读
  4. Golang context 原理分析

    2024-06-14 21:40:02       99 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-14 21:40:02       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-14 21:40:02       106 阅读
  3. 在Django里面运行非项目文件

    2024-06-14 21:40:02       87 阅读
  4. Python语言-面向对象

    2024-06-14 21:40:02       96 阅读

热门阅读

  1. 人工智能在问题答疑领域的应用

    2024-06-14 21:40:02       31 阅读
  2. 从输入URL到页面加载完中间发生了什么?

    2024-06-14 21:40:02       26 阅读
  3. 优化SQL查询的策略和技巧 - AI提供

    2024-06-14 21:40:02       31 阅读
  4. 从 GPT2 到 ChatGPT

    2024-06-14 21:40:02       30 阅读
  5. sqlcoder:7b sqlcoder:15b sqlcoder:70b 有什么区别呢?

    2024-06-14 21:40:02       37 阅读
  6. Android RecyclerView使用

    2024-06-14 21:40:02       26 阅读
  7. C#面:抽象类和接口有什么异同

    2024-06-14 21:40:02       27 阅读