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

go语言的context

发布时间:2024-02-20 (2024-02-20)

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

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

看一些官方包的源码的时候,很多函数的第一个参数都是 ctx context.Context

这个context是干什么的呢?

Context是干什么的

我们都知道 go的协程写起来是非常轻松的

并且在协程里面还能继续开协程

颇有一种一生二,二生三,三生万物的感觉了

这么多协程,协程与协程之间的通信如何解决呢

这个协程我不想要了,我如何关闭它,我们之前没有讲过

那么context来了,就是解决如何关闭协程这个问题的

除了关闭协程,他还能用于传输数据,做到协程与协程之间的桥梁,所以我们叫他 上下文

数据传递

package main

import (
  "context"
  "fmt"
)

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

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

Context 中保存的键值对数据应该是线程安全的,因为它们可能会在多个 goroutine 中同时访问。

取消协程 WithCancel

一个很常见的案例,我有一个获取ip的协程,但是这是一个耗时操作,用户随时可能会取消

如果用户取消了,那么之前那个获取协程的函数就要停止

怎么办呢?

package main

import (
  "context"
  "fmt"
  "sync"
  "time"
)

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
}

截止时间WithDeadline

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

还是上面那个案例

package main

import (
  "context"
  "fmt"
  "time"
)

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判断是超时还是取消
  }
}

超时时间WithTimeout

用法大差不差,也是可以手动取消的

package main

import (
  "context"
  "fmt"
  "time"
)

func main() {
  ctx, _ := context.WithTimeout(context.Background(), 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讲解 https://zhuanlan.zhihu.com/p/628201027

context学习笔记 https://blog.csdn.net/QcloudCommunity/article/details/126476476