Go 并发编程基础:什么是上下文(中)

简介: 在开发过程中,也有这个上下文(Context)的概念,而且上下文也必不可少,缺少上下文,就不能获取完整的程序信息。那么什么是程序中的上下文呢?简单来说,就是在 API 之间或者函数调用之间,除了业务参数信息之外的额外信息。比如,服务器接收到客户端的 HTTP 请求之后,可以把客户端的 IP 地址和端口、客户端的身份信息、请求接收的时间、Trace ID 等信息放入到上下文中,这个上下文可以在后端的方法调用中传递。

3 Context Tree

在实际实现中,我们通常使用派生上下文。我们创建一个父上下文并将其传递到一个层,我们派生一个新的上下文,它添加一些额外的信息并将其再次传递到下一层,依此类推。通过这种方式,我们创建了一个从作为父级的根上下文开始的上下文树。这种结构的优点是我们可以一次性控制所有上下文的取消。如果根信号关闭了上下文,它将在所有派生的上下文中传播,这些上下文可用于终止所有进程,立即释放所有内容。这使得上下文成为并发编程中非常强大的工具。


网络异常,图片无法展示
|

4 创建上下文

4.1 上下文创建函数

我们可以从现有的上下文中创建或派生上下文。顶层(根)上下文是使用 BackgroundTODO 方法创建的,而派生上下文是使用 WithCancel、WithDeadline、WithTimeout 或 WithValue 方法创建的。


所有派生的上下文方法都返回一个取消函数 CancelFunc,但 WithValue 除外,因为它与取消无关。调用 CancelFunc 会取消子项及其子项,删除父项对子项的引用,并停止任何关联的计时器。调用 CancelFunc 失败会泄漏子项及其子项,直到父项被取消或计时器触发。

context.Background() ctx Context

此函数返回一个空上下文。这通常只应在主请求处理程序或顶级请求处理程序中使用。这可用于为主函数、初始化、测试以及后续层或其他 goroutine 派生上下文的时候。

ctx, cancel := context.Background()

context.TODO() ctx Context

此函数返回一个非 nil 的、空的上下文。没有任何值、不会被 cancel,不会超时,也没有截止日期。但是,这也应该仅在您不确定要使用什么上下文或者该函数还不能用于接收上下文时,可以使用这个方法,并且将在将来需要添加时使用。

ctx, cancel := context.TODO()

context.WithValue(parent Context, key, val interface{}) Context

这个函数接受一个上下文并返回一个派生的上下文,其中值 val 与 key 相关联,并与上下文一起经过上下文树。


WithValue 方法其实是创建了一个类型为 valueCtx 的上下文,它的类型定义如下:

type valueCtx struct {
    Context
    key, val interface{}
}


这意味着一旦你得到一个带有值的上下文,任何从它派生的上下文都会得到这个值。该值是不可变的,因此是线程安全的。


提供的键必须是可比较的,并且不应该是字符串类型或任何其他内置类型,以避免使用上下文的包之间发生冲突。 WithValue 的用户应该为键定义自己的类型。为避免在分配给 interface{} 时进行分配,上下文键通常具有具体类型 struct{}。或者,导出的上下文键变量的静态类型应该是指针或接口。

package main
import (
  "context"
  "fmt"
)
type contextKey string
func main() {
  var authToken contextKey = "auth_token"
  ctx := context.WithValue(context.Background(), authToken, "Hello123456")
  fmt.Println(ctx.Value(authToken))
}


运行该代码:

$ go run .           
Hello123456

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

此函数接收父上下文并返回派生上下文,返回 parent 的副本,只是副本中的 Done Channel 是新建的对象,它的类型是 cancelCtx。在这个派生上下文中,添加了一个新的 Done  channel,该 channel 在调用 cancel 函数或父上下文的 Done 通道关闭时关闭。


要记住的一件事是,我们永远不应该在不同的函数或层之间传递这个 cancel ,因为它可能会导致意想不到的结果。创建派生上下文的函数应该只调用取消函数。


下面是一个使用 Done 通道演示 goroutine 泄漏的示例:

package main
import (
  "context"
  "fmt"
  "math/rand"
  "time"
)
func main() {
  rand.Seed(time.Now().UnixNano())
  ctx, cancel := context.WithCancel(context.Background())
  defer cancel()
  for char := range randomCharGenerator(ctx) {
    generatedChar := string(char)
    fmt.Printf("%v\n", generatedChar)
    if generatedChar == "o" {
      break
    }
  }
}
func randomCharGenerator(ctx context.Context) <-chan int {
  char := make(chan int)
  seedChar := int('a')
  go func() {
    for {
      select {
      case <-ctx.Done():
        fmt.Printf("found %v", seedChar)
        return
      case char <- seedChar:
        seedChar = 'a' + rand.Intn(26)
      }
    }
  }()
  return char
}


运行结果:

$ go run .           
a
m
q
c
l
t
o
相关文章
|
2月前
|
Go 调度 开发者
Go语言中的并发编程:深入理解与实践###
探索Go语言在并发编程中的独特优势,揭秘其高效实现的底层机制。本文通过实例和分析,引导读者从基础到进阶,掌握Goroutines、Channels等核心概念,提升并发处理能力。 ###
|
22天前
|
并行计算 安全 Go
Go语言中的并发编程:掌握goroutines和channels####
本文深入探讨了Go语言中并发编程的核心概念——goroutine和channel。不同于传统的线程模型,Go通过轻量级的goroutine和通信机制channel,实现了高效的并发处理。我们将从基础概念开始,逐步深入到实际应用案例,揭示如何在Go语言中优雅地实现并发控制和数据同步。 ####
|
27天前
|
存储 Go 开发者
Go语言中的并发编程与通道(Channel)的深度探索
本文旨在深入探讨Go语言中并发编程的核心概念和实践,特别是通道(Channel)的使用。通过分析Goroutines和Channels的基本工作原理,我们将了解如何在Go语言中高效地实现并行任务处理。本文不仅介绍了基础语法和用法,还深入讨论了高级特性如缓冲通道、选择性接收以及超时控制等,旨在为读者提供一个全面的并发编程视角。
|
23天前
|
安全 Serverless Go
Go语言中的并发编程:深入理解与实践####
本文旨在为读者提供一个关于Go语言并发编程的全面指南。我们将从并发的基本概念讲起,逐步深入到Go语言特有的goroutine和channel机制,探讨它们如何简化多线程编程的复杂性。通过实例演示和代码分析,本文将揭示Go语言在处理并发任务时的优势,以及如何在实际项目中高效利用这些特性来提升性能和响应速度。无论你是Go语言的初学者还是有一定经验的开发者,本文都将为你提供有价值的见解和实用的技巧。 ####
|
25天前
|
Go 调度 开发者
Go语言中的并发编程:深入理解goroutines和channels####
本文旨在探讨Go语言中并发编程的核心概念——goroutines和channels。通过分析它们的工作原理、使用场景以及最佳实践,帮助开发者更好地理解和运用这两种强大的工具来构建高效、可扩展的应用程序。文章还将涵盖一些常见的陷阱和解决方案,以确保在实际应用中能够避免潜在的问题。 ####
|
27天前
|
安全 Go 数据处理
Go语言中的并发编程:掌握goroutine和channel的艺术####
本文深入探讨了Go语言在并发编程领域的核心概念——goroutine与channel。不同于传统的单线程执行模式,Go通过轻量级的goroutine实现了高效的并发处理,而channel作为goroutines之间通信的桥梁,确保了数据传递的安全性与高效性。文章首先简述了goroutine的基本特性及其创建方法,随后详细解析了channel的类型、操作以及它们如何协同工作以构建健壮的并发应用。此外,还介绍了select语句在多路复用中的应用,以及如何利用WaitGroup等待一组goroutine完成。最后,通过一个实际案例展示了如何在Go中设计并实现一个简单的并发程序,旨在帮助读者理解并掌
|
27天前
|
安全 Java Go
Go语言中的并发编程:掌握goroutine与通道的艺术####
本文深入探讨了Go语言中的核心特性——并发编程,通过实例解析goroutine和通道的高效使用技巧,旨在帮助开发者提升多线程程序的性能与可靠性。 ####
|
28天前
|
Go 开发者
Go语言中的并发编程:掌握goroutines和channels####
本文深入探讨了Go语言中并发编程的核心概念,重点介绍了goroutines和channels的工作原理及其在实际开发中的应用。文章通过实例演示如何有效地利用这些工具来编写高效、可维护的并发程序,旨在帮助读者理解并掌握Go语言在处理并发任务时的强大能力。 ####
|
26天前
|
算法 安全 程序员
Go语言的并发编程:深入理解与实践####
本文旨在探讨Go语言在并发编程方面的独特优势及其实现机制,通过实例解析关键概念如goroutine和channel,帮助开发者更高效地利用Go进行高性能软件开发。不同于传统的摘要概述,本文将以一个简短的故事开头,引出并发编程的重要性,随后详细阐述Go语言如何简化复杂并发任务的处理,最后通过实际案例展示其强大功能。 --- ###
|
1月前
|
存储 安全 Go
Go 语言以其高效的并发编程能力著称,主要依赖于 goroutines 和 channels 两大核心机制
Go 语言以其高效的并发编程能力著称,主要依赖于 goroutines 和 channels 两大核心机制。本文介绍了这两者的概念、用法及如何结合使用,实现任务的高效并发执行与数据的安全传递,强调了并发编程中的注意事项,旨在帮助开发者更好地掌握 Go 语言的并发编程技巧。
35 2