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 \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 \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)
怎么用printf输出bool函数值
// close all gourtines
cancel()
// waiting 10 seconds, if the screen does not display <monitorX: xxxx >, all goroutines have been shut down
time.Sleep(10 * time.Second)
println(runtime.NumGoroutine())
println("main program exit")
}
执⾏的结果是:
monitor1: 5
monitor2: 5
monitor1: 2
monitor2: 2
monitor2: 1
monitor1: 1
monitor1: 4
monitor1: 3
monitor2: 4
monitor2: 3
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 \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 \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 \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 \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的作⽤内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!