go语言并发编程(五) ——Context

简介: go语言并发编程(五) ——Context

Context(上下文)

前言

Context是go语言中所提供的一种并发控制的解决方案,相比于管道与WaitGroup,Context可以更好的控制子孙协程以及层次更深的协程。Context本身是一个接口,只要我们实现了该接口都可以被称为上下文,context标准库本身也提供了几个实现:

  • emptyCtx
  • cancelCtx
  • timerCtx
  • valueCtx

什么是Context

在看Context的具体实现之前,先来看看Context接口的定义.

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

我们来看一看这个接口里面所定义的四个方法:

  • Deadline
    该方法有两个返回值,deadline是截止时间,也就是上下文截止的时间,第二个值是是否设置dedline,如果没有则一直为false.
  • Done
    返回值是一个空结构体的只读管道,该管道仅仅起到通知作用,不传递任何数据,当上下文所做的工作要取消的时候,该通道就会被关闭,对于一些不支持取消的上下文,可能会返回nil
  • Err
    该方法会返回一个error,用来表示上下关闭的原因,如果管道没有关闭,则返回nil,如果关闭的话,则返回一个error,用来表示上下文关闭的原因。
  • Value
    该方法返回对应的键值,如果key不存在,或者不支持该方法,就会返回nil。

下面我们来看一个简单的例子:

package main
import (
  "context"
  "fmt"
  "sync"
  "time"
)
var wa sync.WaitGroup
var stop bool
var RW sync.RWMutex
func cpuIInfo(ctx context.Context) {
  defer wa.Done()
  for {
    select {
    case <-ctx.Done():
      fmt.Println("cpu info exit")
      return
    default:
      time.Sleep(2 * time.Second)
      fmt.Println("cpu info")
    }
  }
}
func main() {
  wa.Add(1)
  ctx, cancel := context.WithCancel(context.Background())
  go cpuIInfo(ctx)
  time.Sleep(6 * time.Second)
  cancel()
  fmt.Println("main exit")
  wa.Wait()
}

输出为:

cpu info
cpu info
main exit
cpu info
cpu info exit

或许现在你不是很清楚上面的例子,但是看完今天的博文以后,相信大家就能和好的理解了话不多说,让我们来看一下有关context的具体内容:

emptyCtx

顾名思义,emptyCtx指的就是空的上下文,context包下所有的实现其实都是不对外暴露的,所以我们无法直接创建context.Context,但是go语言提供了对应的函数区创建上下文,例如下面我们可以利用context.Background()context.TODO()函数来创建一个空的上下文:两个函数的具体实现如下:

var{
  background =new(emptyCtx)
  todo =new(emptyCtx)
}
func Background()Context{
  return background
}
func TODO()Context{
  return todo
}

我们再来看看emptyCtx四个函数的实现:

type  emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func () Done() <-chan struct{} {
return nil
}
func () Value(key any) any {
return nil
}
func (emptyContext) Err() error {
return nil
}

我们仔细观察emptyCtx的实现,发现其实emptyCtx仅仅返回了emptyCtx指针.emptyCtx的底层类型是int,之所以不使用空结构体,在之前我们提到过空结构体没有字段,不占用内存,但是我们要求emptyCtx的实例都要有自己的内存地址,而在它的方法中,由于它不能被取消,所以它没有deadline,由于它不能被取值,所以它实现的方法都是返回nil.emptyCtx通常是用来当作最顶层的上下文,在创建其他三种上下文时作为父上下文传入。

valueCtx

valueCtx的实现比较简单,它的内部只包括一对键值对,和一个内嵌的Context字段:

type valueCtx struct{
  Context
  kay,value any
}

它自身也实现了Value方法,基本逻辑其实也很简单:找不到就去喊爸爸(去父上下文找):

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

我们可以来看一个简单的例子:

package main
import (
  "context"
  "sync"
  "time"
)
var w sync.WaitGroup
func main() {
  w.Add(1)
  go Do(context.WithValue(context.Background(), 1, 2))
  w.Wait()
}
func Do(ctx context.Context) {
  ticker := time.NewTimer(2 * time.Second)
  defer w.Done()
  for {
    select {
    case <-ticker.C:
      println("time out")
      return
    case <-ctx.Done():
    default:
      println(ctx.Value(1).(int))
    }
    time.Sleep(100 * time.Millisecond)
  }
}

输出为:

2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
2
time out

valueCtx多用于在多级协程中传递一些数据,无法被取消,因此ctx.Done永远会返回nil,select会忽略掉nil管道。

cancelCtx

cancelCtx以及timerCtx都实现了canceler接口,接口类型如下:

type canceler interface {
  // removeFromParent 表示是否从父上下文中删除自身
  // err 表示取消的原因
  cancel(removeFromParent bool, err, cause error)
  // Done 返回一个管道,用于通知取消的原因
  Done() <-chan struct{}
}

我们查看上面的源码可以看出来:cancel方法本身不对外暴露,但是会在我们创建上下文的时候通过闭包来将其封装成返回值供外界调用,这个在源码中也有所体现:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
  if parent==nil{
    panic("cannot create context from nil parent")
  }
  c:=newCancelCtx(parent)
  //尝试将自身添加进父级的children中
  propagateCancel(parent,&c)
  return &c,func(){
    c.cancel(true,context.Canceled,nil)
  }
}

cancelCtx我们可以理解为一个可取消的上下文,它在创建的时候如果父级实现了canceler,就会将自身添加进父级的children中,否则就一直向上查找。如果所有的父级都没有实现canceler,就会启动一个协程等待父级取消,然后当父级结束时取消当前上下文。当调用cancelFunc时,Done通道将会关闭,该上下文的任何子级也会随之取消,最后会将自身从父级中删除。下面我们来看个例子:

package main
import (
  "context"
  "fmt"
  "sync"
  "time"
)
var w sync.WaitGroup
func main() {
  bkg := context.Background()
  ctx, cancel := context.WithCancel(bkg)
  w.Add(1)
  go func(ctx2 context.Context) {
    defer w.Done()
    for {
      select {
      case <-ctx.Done():
        fmt.Println(ctx.Err())
        return
      default:
        fmt.Println("等待取消中...")
      }
      time.Sleep(time.Millisecond * 200)
    }
  }(ctx)
  time.Sleep(time.Second * 3)
  cancel()
  w.Wait()
}

输出为:

等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
等待取消中...
context canceled

timerCtx

相对于cancelCtx,timerCtx多了超时机制,context包下提供了两种创建的函数:

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

这两个函数的功能类似,前者是指定一个具体的时间而后者则是指定一个时间间隔。timeCtx会在时间到期后自动取消上下文,取消的流程除了要额外的关闭timer之外,基本与cancelCtx一致,我们来看一个简单的示例:

package main
import (
  "context"
  "fmt"
  "sync"
  "time"
)
var w sync.WaitGroup
func main() {
  w.Add(1)
  deadline, ctx := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))
  defer ctx()
  go func(ctx2 context.Context) {
    defer w.Done()
    for {
      select {
      case <-deadline.Done():
        fmt.Println("上下文取消")
        return
      default:
        fmt.Println("等待取消")
      }
      time.Sleep(1 * time.Second)
    }
  }(deadline)
  w.Wait()
}

WithTimeout其实与WithDealine非常相似,它的实现也只是稍微封装了一下并调用WithDeadline,和上面例子中的WithDeadline用法一样,如下:

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
  return WithDeadline(parent, time.Now().Add(timeout))
}

注意:

就跟内存分配后不回收会造成内存泄漏一样,上下文也是一种资源,如果创建了但从来不取消,一样会造成上下文泄露,所以最好避免此种情况的发生。

目录
打赏
0
1
1
0
13
分享
相关文章
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
Go 语言并发编程基础:Goroutine 的创建与调度
Go 语言的 Goroutine 是轻量级线程,由 runtime 管理,具有启动快、占用小、支持高并发的特点。本章介绍 Goroutine 的基本概念、创建方式(如使用 `go` 关键字或匿名函数)、M:N 调度模型及其工作流程,并探讨其在高并发场景中的应用,帮助理解其高效并发的优势。
Go 并发编程基础:无缓冲与有缓冲通道
本章深入探讨Go语言中通道(Channel)的两种类型:无缓冲通道与有缓冲通道。无缓冲通道要求发送和接收必须同步配对,适用于精确同步和信号通知;有缓冲通道通过内部队列实现异步通信,适合高吞吐量和生产者-消费者模型。文章通过示例对比两者的行为差异,并分析死锁风险及使用原则,帮助开发者根据场景选择合适的通道类型以实现高效并发编程。
初探Go语言RPC编程手法
总的来说,Go语言的RPC编程是一种强大的工具,让分布式计算变得简单如同本地计算。如果你还没有试过,不妨挑战一下这个新的编程领域,你可能会发现新的世界。
57 10
|
5月前
|
Go 语言入门指南:切片
Golang中的切片(Slice)是基于数组的动态序列,支持变长操作。它由指针、长度和容量三部分组成,底层引用一个连续的数组片段。切片提供灵活的增减元素功能,语法形式为`[]T`,其中T为元素类型。相比固定长度的数组,切片更常用,允许动态调整大小,并且多个切片可以共享同一底层数组。通过内置的`make`函数可创建指定长度和容量的切片。需要注意的是,切片不能直接比较,只能与`nil`比较,且空切片的长度为0。
121 3
Go 语言入门指南:切片
|
5月前
|
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了
本文探讨了如何利用 Go 语言中的 Bloom Filter 算法提升公司局域网管理系统的性能。Bloom Filter 是一种高效的空间节省型数据结构,适用于快速判断元素是否存在于集合中。文中通过具体代码示例展示了如何在 Go 中实现 Bloom Filter,并应用于局域网的 IP 访问控制,显著提高系统响应速度和安全性。随着网络规模扩大和技术进步,持续优化算法和结合其他安全技术将是企业维持网络竞争力的关键。
102 2
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了
|
5月前
|
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
138 20
eino — 基于go语言的大模型应用开发框架(二)
本文介绍了如何使用Eino框架实现一个基本的LLM(大语言模型)应用。Eino中的`ChatModel`接口提供了与不同大模型服务(如OpenAI、Ollama等)交互的统一方式,支持生成完整响应、流式响应和绑定工具等功能。`Generate`方法用于生成完整的模型响应,`Stream`方法以流式方式返回结果,`BindTools`方法为模型绑定工具。此外,还介绍了通过`Option`模式配置模型参数及模板功能,支持基于前端和用户自定义的角色及Prompt。目前主要聚焦于`ChatModel`的`Generate`方法,后续将继续深入学习。
695 7
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问