golang中context的作用详解

 更新时间:2021年01月08日 14:24:10   作者:梦回故里  
这篇文章主要介绍了golang中context的作用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

当一个goroutine可以启动其他goroutine,而这些goroutine可以启动其他goroutine,依此类推,则第一个goroutine应该能够向所有其它goroutine发送取消信号。

上下文包的唯一目的是在goroutine之间执行取消信号,而不管它们如何生成。上下文的接口定义为:

type Context interface {
 Deadline() (deadline time.Time, ok bool)
 Done() <- chan struct{}
 Err() error
 Value(key interface{}) interface{}
}
  • Deadline:第一个值是截止日期,此时上下文将自动触发“取消”操作。第二个值是布尔值,true表示设置了截止日期,false表示未设置截止时间。如果没有设置截止日期,则必须手动调用cancel函数来取消上下文。
  • Done:返回一个只读通道(仅在取消后),键入struct {},当该通道可读时,表示父上下文已经发起了取消请求,根据此信号,开发人员可以执行一些清除操作,退出goroutine
  • Err:返回取消上下文的原因
  • Value:返回绑定到上下文的值,它是一个键值对,因此您需要传递一个Key来获取相应的值,此值是线程安全的

要创建上下文,必须指定父上下文。两个内置上下文(背景和待办事项)用作顶级父上下文:

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

背景,主要ü在主函数,初始化和测试代码的sed,是树结构中,根上下文,这是不能被取消的顶层语境。TODO,当您不知道要使用什么上下文时,可以使用它。它们本质上都是emptyCtx类型,都是不可取消的,没有固定的期限,也没有为Context赋任何值:键入emptyCtx int

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 interface{}) interface{} {
 return nil
}

上下文包还具有几个常用功能:func WithCancel(父上下文)(ctx上下文,取消CancelFunc)func WithDeadline(父上下文,截止时间.Time)(上下文,CancelFunc)func WithTimeout(父上下文,超时时间。持续时间)(上下文,CancelFunc)func WithValue(父上下文,键,val接口{})上下文

请注意,这些方法意味着可以一次继承上下文以实现其他功能,例如,使用WithCancel函数传入根上下文,它会创建一个子上下文,该子上下文具有取消上下文的附加功能,然后使用此方法将context(context01)作为父上下文,并将其作为第一个参数传递给WithDeadline函数,与子context(context01)相比,获得子context(context02),它具有一个附加功能,可在之后自动取消上下文最后期限。

WithCancel

对于通道,尽管通道也可以通知许多嵌套的goroutine退出,但通道不是线程安全的,而上下文是线程安全的。

例如:

package main
import (
 "runtime"
 "fmt"
 "time"
 "context"
)
func monitor2(ch chan bool, index int) {
 for {
  select {
  case v := <- ch:
   fmt.Printf("monitor2: %v, the received channel value is: %v, ending\n", index, v)
   return
  default:
   fmt.Printf("monitor2: %v in progress...\n", index)
   time.Sleep(2 * time.Second)
  }
 }
}
func monitor1(ch chan bool, index int) {
 for {
  go monitor2(ch, index)
  select {
  case v := <- ch:
   // this branch is only reached when the ch channel is closed, or when data is sent(either true or false)
   fmt.Printf("monitor1: %v, the received channel value is: %v, ending\n", index, v)
   return
  default:
   fmt.Printf("monitor1: %v in progress...\n", index)
   time.Sleep(2 * time.Second)
  }
 }
}
func main() {
 var stopSingal chan bool = make(chan bool, 0)
 for i := 1; i <= 5; i = i + 1 {
  go monitor1(stopSingal, i)
 }
 time.Sleep(1 * time.Second)
 // close all gourtines
 cancel()
 // waiting 10 seconds, if the screen does not display <monitorX: xxxx in progress...>, all goroutines have been shut down
 time.Sleep(10 * time.Second)
 println(runtime.NumGoroutine())
 println("main program exit!!!!")
}

执行的结果是:

monitor1: 5 in progress...
monitor2: 5 in progress...
monitor1: 2 in progress...
monitor2: 2 in progress...
monitor2: 1 in progress...
monitor1: 1 in progress...
monitor1: 4 in progress...
monitor1: 3 in progress...
monitor2: 4 in progress...
monitor2: 3 in progress...
monitor1: 4, the received channel value is: false, ending
monitor1: 3, the received channel value is: false, ending
monitor2: 2, the received channel value is: false, ending
monitor2: 1, the received channel value is: false, ending
monitor1: 1, the received channel value is: false, ending
monitor2: 5, the received channel value is: false, ending
monitor2: 3, the received channel value is: false, ending
monitor2: 3, the received channel value is: false, ending
monitor2: 4, the received channel value is: false, ending
monitor2: 5, the received channel value is: false, ending
monitor2: 1, the received channel value is: false, ending
monitor1: 5, the received channel value is: false, ending
monitor1: 2, the received channel value is: false, ending
monitor2: 2, the received channel value is: false, ending
monitor2: 4, the received channel value is: false, ending
1
main program exit!!!!

这里使用一个通道向所有goroutine发送结束通知,但是这里的情况相对简单,如果在一个复杂的项目中,假设多个goroutine有某种错误并重复执行,则可以重复关闭或关闭该通道通道,然后向其写入值,从而触发运行时恐慌。这就是为什么我们使用上下文来避免这些问题的原因,以WithCancel为例:

package main
import (
 "runtime"
 "fmt"
 "time"
 "context"
)
func monitor2(ctx context.Context, number int) {
 for {
  select {
  case v := <- ctx.Done():
   fmt.Printf("monitor: %v, the received channel value is: %v, ending\n", number,v)
   return
  default:
   fmt.Printf("monitor: %v in progress...\n", number)
   time.Sleep(2 * time.Second)
  }
 }
}
func monitor1(ctx context.Context, number int) {
 for {
  go monitor2(ctx, number)
  select {
  case v := <- ctx.Done():
   // this branch is only reached when the ch channel is closed, or when data is sent(either true or false)
   fmt.Printf("monitor: %v, the received channel value is: %v, ending\n", number, v)
   return
  default:
   fmt.Printf("monitor: %v in progress...\n", number)
   time.Sleep(2 * time.Second)
  }
 }
}
func main() {
 var ctx context.Context = nil
 var cancel context.CancelFunc = nil
 ctx, cancel = context.WithCancel(context.Background())
 for i := 1; i <= 5; i = i + 1 {
  go monitor1(ctx, i)
 }
 time.Sleep(1 * time.Second)
 // close all gourtines
 cancel()
 // waiting 10 seconds, if the screen does not display <monitor: xxxx in progress>, all goroutines have been shut down
 time.Sleep(10 * time.Second)
 println(runtime.NumGoroutine())
 println("main program exit!!!!")
}

WithTimeout和WithDeadline

WithTimeout和WithDeadline在用法和功能上基本相同,它们都表示上下文将在一定时间后自动取消,唯一的区别可以从函数的定义中看出,传递给WithDeadline的第二个参数是类型time.Duration类型,它是一个相对时间,表示取消超时后的时间。例:

package main
import (
 "runtime"
 "fmt"
 "time"
 "context"
)
func monitor2(ctx context.Context, index int) {
 for {
  select {
  case v := <- ctx.Done():
   fmt.Printf("monitor2: %v, the received channel value is: %v, ending\n", index, v)
   return
  default:
   fmt.Printf("monitor2: %v in progress...\n", index)
   time.Sleep(2 * time.Second)
  }
 }
}
func monitor1(ctx context.Context, index int) {
 for {
  go monitor2(ctx, index)
  select {
  case v := <- ctx.Done():
   // this branch is only reached when the ch channel is closed, or when data is sent(either true or false)
   fmt.Printf("monitor1: %v, the received channel value is: %v, ending\n", index, v)
   return
  default:
   fmt.Printf("monitor1: %v in progress...\n", index)
   time.Sleep(2 * time.Second)
  }
 }
}
func main() {
 var ctx01 context.Context = nil
 var ctx02 context.Context = nil
 var cancel context.CancelFunc = nil
 ctx01, cancel = context.WithCancel(context.Background())
 ctx02, cancel = context.WithDeadline(ctx01, time.Now().Add(1 * time.Second)) // If it's WithTimeout, just change this line to "ctx02, cancel = context.WithTimeout(ctx01, 1 * time.Second)"
 defer cancel()
 for i := 1; i <= 5; i = i + 1 {
  go monitor1(ctx02, i)
 }
 time.Sleep(5 * time.Second)
 if ctx02.Err() != nil {
  fmt.Println("the cause of cancel is: ", ctx02.Err())
 }
 println(runtime.NumGoroutine())
 println("main program exit!!!!")
}

WithValue

一些必需的元数据也可以通过上下文传递,该上下文将附加到上下文中以供使用。元数据作为键值传递,但请注意,键必须具有可比性,并且值必须是线程安全的。

package main
import (
 "runtime"
 "fmt"
 "time"
 "context"
)
func monitor(ctx context.Context, index int) {
 for {
  select {
  case <- ctx.Done():
   // this branch is only reached when the ch channel is closed, or when data is sent(either true or false)
   fmt.Printf("monitor %v, end of monitoring. \n", index)
   return
  default:
   var value interface{} = ctx.Value("Nets")
   fmt.Printf("monitor %v, is monitoring %v\n", index, value)
   time.Sleep(2 * time.Second)
  }
 }
}
func main() {
 var ctx01 context.Context = nil
 var ctx02 context.Context = nil
 var cancel context.CancelFunc = nil
 ctx01, cancel = context.WithCancel(context.Background())
 ctx02, cancel = context.WithTimeout(ctx01, 1 * time.Second)
 var ctx03 context.Context = context.WithValue(ctx02, "Nets", "Champion") // key: "Nets", value: "Champion"

 defer cancel()
 for i := 1; i <= 5; i = i + 1 {
  go monitor(ctx03, i)
 }
 time.Sleep(5 * time.Second)
 if ctx02.Err() != nil {
  fmt.Println("the cause of cancel is: ", ctx02.Err())
 }
 println(runtime.NumGoroutine())
 println("main program exit!!!!")
}

关于上下文,还有一些注意事项:不要将Context存储在结构类型中,而是将Context明确传递给需要它的每个函数,并且Context应该是第一个参数。

即使函数允许,也不要传递nil Context,或者如果您不确定要使用哪个Context,请传递context。不要将可能作为函数参数传递给上下文值的变量传递。

到此这篇关于golang中context的作用的文章就介绍到这了,更多相关golang中context的作用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • GO语言类型查询类型断言示例解析

    GO语言类型查询类型断言示例解析

    这篇文章主要为大家介绍了GO语言类型判断及类型断言,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-04-04
  • Go语言panic和recover的用法实例

    Go语言panic和recover的用法实例

    panic()和recover()是Go语言中用于处理错误的两个重要函数,本文主要介绍了Go语言panic和recover的用法实例,panic()用于中止程序并引发panic,而recover()用于捕获panic并恢复程序的执行,感兴趣的可以了解一下
    2024-01-01
  • GoLang strings.Builder底层实现方法详解

    GoLang strings.Builder底层实现方法详解

    自从学习go一个月以来,我多少使用了一下strings.Builder,略有心得。你也许知道它,特别是你了解bytes.Buffer的话。所以我在此分享一下我的心得,并希望能对你有所帮助
    2022-10-10
  • Golang标准库binary详解

    Golang标准库binary详解

    这篇文章主要介绍了Golang标准库binary的相关资料,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-05-05
  • logrus日志自定义格式操作

    logrus日志自定义格式操作

    这篇文章主要介绍了logrus日志自定义格式操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • 创建Go工程化项目布局详解

    创建Go工程化项目布局详解

    这篇文章主要介绍了创建Go工程化项目布局详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • Go并发编程实践

    Go并发编程实践

    并发编程一直是Golang区别与其他语言的很大优势,也是实际工作场景中经常遇到的。近日笔者在组内分享了我们常见的并发场景,及代码示例,以期望大家能在遇到相同场景下,能快速的想到解决方案,或者是拿这些方案与自己实现的比较,取长补短。现整理出来与大家共享
    2017-01-01
  • golang 检查网络状态是否正常的方法

    golang 检查网络状态是否正常的方法

    今天小编就为大家分享一篇golang 检查网络状态是否正常的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-07-07
  • Golang拾遗之实现一个不可复制类型详解

    Golang拾遗之实现一个不可复制类型详解

    在这篇文章中我们将实现一个无法被复制的类型,顺便加深对引用类型、值传递以及指针的理解。文中的示例代码讲解详细,感兴趣的可以了解一下
    2023-02-02
  • Golang算法问题之整数拆分实现方法分析

    Golang算法问题之整数拆分实现方法分析

    这篇文章主要介绍了Golang算法问题之整数拆分实现方法,结合实例形式分析了Go语言数值运算与数组遍历相关操作技巧,需要的朋友可以参考下
    2017-02-02

最新评论