【Go】Context

https://www.fengfengzhidao.com/article/WdlGxI0BEG4v2tWkq3bD#go%E8%AF%AD%E8%A8%80%E7%9A%84context
https://blog.csdn.net/weixin_52690231/article/details/124518402
https://blog.csdn.net/m0_57960197/article/details/132529334 基于源码
学一点,整一点,基本都是综合别人的,弄成我能理解的内容

Context

在 Go 语言中,Context 是一个非常重要的概念,它用于在不同的 goroutine 之间传递请求域的相关数据,并且可以用来控制 goroutine 的生命周期和取消操作。

type Context interface {
    Deadline() (deadline time.Time, ok bool)  //用于获取 Context 的截止时间,
    Done() <-chan struct{}   //用于返回一个只读的 channel,用于通知当前 Context 是否已经被取消
    Err() error    //用于获取 Context 取消的原因
    Value(key any) any  //用于获取 Context 中保存的键值对数据
}

Context使用

Background、TODO

//context/context.go
var (
  background = new(emptyCtx)
  todo       = new(emptyCtx)
)

func Background() Context {
  return background
}

func TODO() Context {
  return todo
}

Background函数会创建一个没有Deadline、没有Value,也不能被Cancel的emptyCtx。通常在一个请求的初始化阶段用Background()创建最顶层的根Context。

ctx := context.Background()

TODO函数和Background一样,也会创建一个emptyCtx,官方文档建议在"本来应该使用外层传递的ctx,而外层却没有传递"的地方使用,就像函数名的含义一样,留下一个TODO。

数值传递 WithValue

func main() {
  ctx := context.WithValue(context.Background(), "name", "fengfeng")
  GetUser(ctx)
}

func GetUser(ctx context.Context) {
  // 获取用户名
  fmt.Println(ctx.Value("name"))
}

虽然 context.WithValue 函数允许存储任何类型的值,但是为了保证线程安全,存储的值应该是线程安全的。这意味着值本身不应该是可变的,或者应该使用互斥锁等机制来保护对它的并发访问。如果存储的值是可变的,并且可能会被多个 goroutine 同时修改,那么必须确保对这些值的访问是线程安全的。 (线程安全意味着在多线程环境下,对共享资源的访问不会引发竞态条件,能够保证数据的一致性和完整性。)

var key string="name"

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    // 附加值
    valueCtx:=context.WithValue(ctx,key,"【监控 1】")
    go watch(valueCtx)
    time.Sleep(10 * time.Second)
    fmt.Println("可以了, 通知监控停止")
    cancel()
    // 为了检测监控过是否停止, 如果没有监控输出, 就表示停止了
    time.Sleep(5 * time.Second)
}

func watch(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            // 取出值
            fmt.Println(ctx.Value(key),"监控退出, 停止了。..")
            return
        default:
            // 取出值
            fmt.Println(ctx.Value(key),"goroutine 监控中。..")
            time.Sleep(2 * time.Second)
        }
    }
}

取消协程 WithCancel

var wait = sync.WaitGroup{}

func main() {
  t1 := time.Now()
  wait.Add(1)
  ctx, cancel := context.WithCancel(context.Background())
  go func() {
    ip, err := GetIp(ctx)
    fmt.Println(ip, err)
  }()
  wait.Add(1)
  go func() {
    time.Sleep(2 * time.Second)
    cancel()
    wait.Done()
  }()

  wait.Wait()
  fmt.Println("执行完成", time.Since(t1))
}

func GetIp(ctx context.Context) (ip string, err error) {

  go func() {
    select {
    case <-ctx.Done():
      fmt.Println("取消", ctx.Err().Error())
      err = ctx.Err()
      wait.Done()
      return
    }
  }()

  time.Sleep(3 * time.Second)
  ip = "192.168.200.1"
  wait.Done()
  return
}

WithCancel函数会基于传入的parent创建一个可以Cancel的ctx,与cancel函数一起返回。调用cancel函数就会将这个新的ctx Cancel掉,所有基于此ctx创建的子孙Context也会一并被Cancel掉。

func main() {
    var wg sync.WaitGroup
    ctx := context.Background()
    ctx1, cancel = context.WithCancel(ctx)

    wg.Add(1)
    go func() {
        defer wg.Done()
        tick := time.NewTicker(300 * time.Millisecond)
        for {
            select {
            case <-ctx1.Done():
                fmt.Println(ctx1.Err())
                return
            case t := <-tick.C:
                fmt.Println(t.Nanosecond())
            }
        }
    }()
    time.Sleep(time.Second)
    cancel()
    wg.Wait()
}

截止时间 WithDeadline
除了使用 WithCancel() 方法进行取消操作之外,Context 还可以被用来设置截止时间,以便在超时的情况下取消请求

func main() {
  ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
  go GetIp(ctx)

  // 5秒到了,手动结束协程
  time.Sleep(5 * time.Second)
  //cancel() // 可以手动取消,也可让他自然超时

  // 模拟主线程阻塞
  time.Sleep(1 * time.Second)

}

func GetIp(ctx context.Context) {
  fmt.Println("获取ip中")
  // 等待请求完成或者被取消
  select {
  case <-ctx.Done():
    // 请求被取消
    fmt.Println("请求超时或被取消", ctx.Err()) // 可以通过err判断是超时还是取消
  }
}

Context 使用原则

  • 不要把 Context 放在结构体中, 要以参数的方式传递
  • 以 Context 作为参数的函数方法, 应该把 Context 作为第一个参数, 放在第一位。
  • 给一个函数方法传递 Context 的时候, 不要传递 nil, 如果不知道传递什么, 就使用 context.TODO
  • Context 的 Value 相关方法应该传递必须的数据, 不要什么数据都使用这个传递
  • 保证Context 是线程安全的, 可以放心的在多个 goroutine 中传递

源码

https://blog.csdn.net/m0_57960197/article/details/132529334

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
    cause    error                 // set to non-nil by the first cancel call
}

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

Context:内置了一个context为父context,可见。
mu:内置了一把锁,用以协调并发场景下的资源获取
实际类型为chan struct{} 用以反映cancelCtx生命周期的通道
children:一个set指向cancelCtx的所有子context
err:记录当前cancelCtx的错误

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中done chan倘若已存在则直接返回
加锁后,检查chan是否存在,存在返回 双重检查
初始化chan存储到done中返回 懒加载

相关推荐

最近更新

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

    2024-03-30 17:42:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-30 17:42:01       100 阅读
  3. 在Django里面运行非项目文件

    2024-03-30 17:42:01       82 阅读
  4. Python语言-面向对象

    2024-03-30 17:42:01       91 阅读

热门阅读

  1. IO流主要有哪些?

    2024-03-30 17:42:01       39 阅读
  2. 实现文件下载

    2024-03-30 17:42:01       37 阅读
  3. Nginx专栏分享

    2024-03-30 17:42:01       47 阅读
  4. DNS 域名解析流程

    2024-03-30 17:42:01       42 阅读
  5. vue3路由跳转

    2024-03-30 17:42:01       42 阅读
  6. C语言共用体和枚举

    2024-03-30 17:42:01       38 阅读
  7. C#-非托管代码

    2024-03-30 17:42:01       43 阅读
  8. sql sqlserver常用日期函数

    2024-03-30 17:42:01       48 阅读
  9. 多进程和多线程

    2024-03-30 17:42:01       43 阅读
  10. 一些常见的与 Vim 相关的文件类型及其描述

    2024-03-30 17:42:01       42 阅读
  11. C++ 各种数据结构定义以及初始化

    2024-03-30 17:42:01       37 阅读