脚本宝典收集整理的这篇文章主要介绍了12-常用标准库之-context,脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
Context,翻译为"上下文",context包定义了Context接口类型,其接口方法定义了跨API和进程之间的执行最后期限、取消信号和其他请求范围的值
在并发程序中,由于超时、取消操作或者一些异常情况,往往需要进行抢占操作或者中断后续操作
context常用的使用场景:
- 一个请求对应多个goroutine之间的数据交互
- 超时控制
- 上下文控制
context.Context
是一个接口,该接口定义了四个需要实现的方法。具体如下:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
其中:
Deadline
:是获取设置的截止时间的意思,第一个返回值是截止时间,到了这个时间点,Context 会自动发起取消请求;第二个返回值 ok==false
时表示没有设置截止时间,如果需要取消的话,需要调用取消函数进行取消。Done
:该方法返回一个只读的 chan,类型为 struct{},我们在 goroutine 中,如果该方法返回的 chan 可以读取,则意味着parent context
已经发起了取消请求,我们通过 Done 方法收到这个信号后,就应该做清理操作,然后退出 goroutine,释放资源。Err
方法返回取消的错误原因,因为什么 Context 被取消。Value
方法获取该 Context 上绑定的值,是一个键值对,所以要通过一个 Key 才可以获取对应的值,这个值一般是线程安全的context包提供两种顶级的上下文类型,这两个内置的上下文对象作为最顶层的partent context
,衍生出更多的子上下文对象:
context.Background()返回非零的空上下文。它从不被取消,没有值,也没有最后期限。它通常由主函数、初始化和测试使用,并且作为传入请求的顶级上下文。
context.TODO()返回非零的空上下文。当不清楚要使用哪个上下文或者它还不可用时(,应该使用context.TODO()。
本质来讲两者区别不大,其源码实现是一样的,只不过使用场景不同,context.Background()通常由主函数、初始化和测试使用,是顶级Context;context.TODO()通常用于主协程外的其他协程向下传递,分析工具可识别它在调用栈中传播
除以上两种顶级Context类型,context包提供四种创建可派生Context类型的函数
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithCancel函数返回具有新done通道的父级副本。当调用返回的cancel函数或关闭父上下文的done通道时(以先发生者为准),将关闭返回的上下文的done通道。 取消此上下文将释放与其关联的资源,因此代码应在此上下文中运行的操作完成后立即调用Cancel。
示例通过context控制多个协程停止:
package main
import (
"context"
"fmt"
"time"
)
func task(ctx context.Context,s string) {
for {
select {
case <-ctx.Done():
fmt.Println("task:我收到取消指令,我结束了")
return
default:
fmt.Println(s)
time.Sleep(1 * time.Second)
}
}
}
func main() {
ctx, cancle := context.WithCancel(context.Background())
go task(ctx,"lqz is Nb")
go task(ctx,"lqz is handsome")
time.Sleep(5*time.Second) // 睡个5s钟,发现上面两句话不停打印
cancle() // 通过ctx控制,上面两个go协程关闭
time.Sleep(5*time.Second) // 睡个5s钟,发现确实被停止了,不打印了
}
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
// 注意第二个参数是时间对象
WithDeadline函数返回父上下文的副本,其截止时间调整为不迟于d。如果父上下文的截止时间早于d,则WithDeadline(Parent,d)在语义上等同于父上下文。当截止时间到期、调用返回的cancel函数或关闭父上下文的done通道(以先发生者为准)时,返回的上下文的done通道将关闭。 取消此上下文将释放与其关联的资源,因此代码应在此上下文中运行的操作完成后立即调用Cancel。
官方使用示例: 这个例子传递一个具有任意截止时间的上下文,告诉一个阻塞函数一旦到达它就应该放弃它的工作。
package main
import (
"context"
"fmt"
"time"
)
func task(ctx context.Context) {
select {
case <-ctx.Done():
fmt.Println("task:我收到取消指令,我结束了")
fmt.Println(ctx.Err()) // 到时间取消,会打印context deadline exceeded,收到cancle的取消,会打印context canceled
case <-time.After(1 * time.Second):
fmt.Println("1s时间到了")
fmt.Println(ctx.Err()) // 执行到此,如果还没到结束时间,Err为nil
}
}
func main() {
//t := time.Now().Add(50 * time.Millisecond) // 50毫秒后的时间, 由于定时50毫秒取消,所以,到定时的时间,就结束,
t := time.Now().Add(2 * time.Second) // 如果定时2s,先到这个时间,先到task中监听的1s到时
ctx, cancle := context.WithDeadline(context.Background(), t)
go task(ctx)
//time.Sleep(1*time.Second)
cancle()
time.Sleep(2*time.Second)
}
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
// 注意第二个参数是time.Duration 时间间隔
WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout))。取消此上下文将释放与其关联的资源,因此代码应在此上下文中运行的操作完成后立即调用取消:
这个例子传递一个带有超时的上下文,告诉一个阻塞函数它应该在超时结束后放弃它的工作。
package main
import (
"context"
"fmt"
"time"
)
func task(ctx context.Context) {
select {
case <-ctx.Done():
fmt.Println("task:我结束了")
// cancle函数取消会打印context canceled
// 到时间取消会打印:context deadline exceeded
fmt.Println(ctx.Err())
case <-time.After(1 * time.Second):
fmt.Println("1s时间到了")
fmt.Println(ctx.Err()) // 执行到此,如果还没到结束时间,Err为nil
}
}
func main() {
//ctx, cancle := context.WithTimeout(context.Background(), 1*time.Second) // 打印
ctx, cancle := context.WithTimeout(context.Background(), 2*time.Second)
go task(ctx)
time.Sleep(3*time.Second)
cancle()
time.Sleep(3*time.Second)
}
func WithValue(parent Context, key, val interface{}) Context
WithValue返回父级的副本,可为上下文设置一个键值对。 只对传输进程和API的请求范围数据使用上下文值,而不用于向函数传递可选参数。 提供的键必须是可比较的,并且不应是字符串或任何其他内置类型,以避免使用上下文的包之间发生冲突。WithValue的用户应该为键定义自己的类型。为了避免在分配给接口时进行分配,上下文键通常具有具体的类型结构。或者,导出的上下文键变量的静态类型应该是指针或接口。
示例:
package main
import (
"context"
"fmt"
"time"
)
func task(ctx context.Context) {
fmt.Println(ctx.Value("name"))
select {
case <-ctx.Done():
fmt.Println("task:我结束了")
fmt.Println(ctx.Err())
case <-time.After(1 * time.Second):
fmt.Println("1s时间到了")
fmt.Println(ctx.Err())
}
}
func main() {
ctx, _ := context.WithTimeout(context.Background(), 1*time.Second) // 1s超时的ctx
ctx = context.WithValue(ctx, "name", "lqz")
go task(ctx)
time.Sleep(5*time.Second)
}
使用context
包来实现线程安全退出或超时的控制:控制10s后,所有协程退出
package main
import (
"context"
"fmt"
"strconv"
"sync"
"time"
)
func task(ctx context.Context, s string, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
fmt.Println(s, "--->我结束了")
//fmt.Println(ctx.Err())
return
default:
fmt.Println(s)
time.Sleep(1 * time.Second)
}
}
}
func main() {
var wg sync.WaitGroup
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
for i := 0; i < 10; i++ {
wg.Add(1)
s := fmt.Sprintf("我是第:%v 个任务", strconv.Itoa(i))
go task(ctx, s, &wg)
}
wg.Wait()
}
当并发体超时或main
主动停止工作者Goroutine时,每个工作者都可以安全退出。
Go语言是带内存自动回收特性的,因此内存一般不会泄漏。当main
函数不再使用管道时后台Goroutine有泄漏的风险。我们可以通过context
包来避免这个问题,下面是防止内存泄露的素数筛实现:
// 返回生成自然数序列的管道: 2, 3, 4, ...
func GenerateNatural(ctx context.Context) chan int {
ch := make(chan int)
go func() {
for i := 2; ; i++ {
select {
//父协程cancel()时安全退出该子协程
case <- ctx.Done():
return
//生成的素数发送到管道
case ch <- i:
}
}
}()
return ch
}
// 管道过滤器: 删除能被素数整除的数
func PrimeFilter(ctx context.Context, in <-chan int, prime int) chan int {
out := make(chan int)
go func() {
for {
if i := <-in; i%prime != 0 {
select {
//父协程cancel()时安全退出该子协程
case <- ctx.Done():
return
case out <- i:
}
}
}
}()
return out
}
func main() {
// 使用一个可由父协程控制子协程安全退出的Context。
ctx, cancel := context.WithCancel(context.Background())
ch := GenerateNatural(ctx) // 自然数序列: 2, 3, 4, ...
for i := 0; i < 100; i++ {
// 新出现的素数打印出来
prime := <-ch
fmt.Printf("%v: %vn", i+1, prime)
// 基于新素数构造的过滤器
ch = PrimeFilter(ctx, ch, prime)
}
//输出100以内符合要求的素数后安全退出所有子协程
cancel()
}
当main函数完成工作前,通过调用cancel()
来通知后台Goroutine退出,这样就避免了Goroutine的泄漏。
以上是脚本宝典为你收集整理的12-常用标准库之-context全部内容,希望文章能够帮你解决12-常用标准库之-context所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。