Golang上下文context

上篇内容我们主要讲解了net/http标准库的使用,其中包含如何创建POST请求、GET请求以及如何携带参数的请求。

Context介绍

context释义为上下文,在我们使用goroutine时一般使用context来进行元数据的传递,非元数据不建议使用context来进行传递。那么我们主要是用context用来做什么呢?其实我们主要是是用来在多个goroutine中传递取消信号,调用链路信息等。

使用建议

  1. 不要在结构体中存储context,将context类型作为函数的第一个参数,一般命名为ctx

  2. 不要向函数中传递nilcontext,如果不知道使用那个可以使用TODO进行占位使用都一样。

  3. 不要将函数所需的非元数据塞到context中。

  4. 同一个context可能会被传递到多个goroutine中,context对于多个goroutine是安全的。

context接口

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

context方法

我们可以看到context接口中有四个方法。

  • Deadline():返回值deadline表示当前context应该结束的时间,例如2sok表示是否有结束时间。

  • Done():返回一个只读的单向channel,当超时或者调用cancel(创建子context的一个返回函数)方法时,将会触发该方法。

  • Err():context被取消的原因。

  • Value():context共享数据存储。

创建根(父)context

  • context.Background():返回一个非nil的空context,没有任何值,不会被cancel,不会超时,没有截至日期。

  • context.TODO():返回一个非nil的 空context,没有任何值,不会被cancel,不会超时,没有截至日期。当不知道使用那个context时,用它。

图片

创建子context

  • WithValue:通过该函数可以在context中传递键值对,用于跨goroutine传递请求范围的值,但是要注意,这不是用于传递请求相关数据的首选方法,而是应该用于传递相关元数据。

    图片

  • WithCancle:通过该函数可以创建一个可取消的context并返回一个新的context和一个cancelFunc。当调用CancelFunc时,与该context相关联的所有goroutines都会收到取消信号.

    图片

  • WithTimeout:通过该函数可以创建一个带有超时时间的context。在指定的时间后过期,context会自动取消 。不要忘记defer cancel()

    图片

  • WithDeadline:通过该函数可以创建一个带有特定截至时间的context,在截至时间之后context会自动取消  不要忘记defer cancel()哦。

    图片

案例演示

WithValue

package main

import (
"context"
"fmt"
)

func main() {
parentContext := context.Background()
ctx1 := context.WithValue(parentContext, "key1", "value1")
ctx11 := context.WithValue(ctx1, "key11", "value11")
// 这里的儿子与孙子都是基于parentContext来说的哦
fmt.Println("儿子的money:", ctx1.Value("key1"))
fmt.Println("儿子将money给孙子了:", ctx11.Value("key1"))
fmt.Println("孙子个人money:", ctx11.Value("key11"))
fmt.Println("孙子想要儿子的money:", ctx1.Value("key11"))
}

运行结果为:

儿子的money: value1
儿子将money给孙子了: value1
孙子个人money: value11
孙子想要儿子的money: <nil>

说明:通过上边的案例我们应该可以看懂context中的value传递。

WithCancle

package main

import (
"context"
"fmt"
"time"
)

func main() {
// 这样写不要看不懂哦,只是将父context直接传入了
ctx, cancel := context.WithCancel(context.Background())
go fun1(ctx)
time.Sleep(6 * time.Second)
fmt.Println("使用cancel停止goroutine")
cancel()
time.Sleep(2 * time.Second)
fmt.Println("main")
}

func fun1(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("goroutine退出了哦", ctx.Err())
return
default:
fmt.Println("goroutine持续运行中")
time.Sleep(2 * time.Second)
}
}

}

运行结果为:

goroutine持续运行中
goroutine持续运行中
goroutine持续运行中
使用cancel停止goroutine
goroutine退出了哦 context canceled
main

说明:从上述运行结果我们可以看出,当调用cancelgoroutine停止了。并打印了退出的原因(ctx.Err())。

WithTimeout

package main

import (
"context"
"fmt"
"time"
)

func main() {
// 这样写不要看不懂哦,只是将父context直接传入了
ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second)
go fun1(ctx)
time.Sleep(18 * time.Second)
defer cancel()
time.Sleep(2 * time.Second)
fmt.Println("main")
}

func fun1(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("goroutine退出了哦", ctx.Err())
return
default:
time.Sleep(2 * time.Second)
fmt.Println("goroutine持续运行中")

}
}

}

运行结果为:

goroutine持续运行中
goroutine持续运行中
goroutine持续运行中
goroutine退出了哦 context deadline exceeded
main

说明:通过上述代码的运行结果我们可以发现,当运行超过我们设置的时间(6*time.Second)时,会取消goroutine的运行,这也是我们打印的结果为什么时3goroutine持续运行中

WithDeadline

package main

import (
"context"
"fmt"
"time"
)

func main() {
// 这样写不要看不懂哦,只是将父context直接传入了
d := time.Now().Add(6 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), d)
go fun1(ctx)
defer cancel()
time.Sleep(10 * time.Second)
fmt.Println("main")
}

func fun1(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("goroutine退出了哦", ctx.Err())
return
default:
time.Sleep(2 * time.Second)
fmt.Println("goroutine持续运行中")

}
}

}

运行结果为:

goroutine持续运行中
goroutine持续运行中
goroutine持续运行中
goroutine退出了哦 context deadline exceeded
main

说明:根据打印结果我们可以发现,其实WithDeadlineWithTimeout差不多,只不过WithTimeout设置的是运行的时间,而WithDeadline表示goroutine运行到那个时间节点。

posted @ 2024-10-11 16:13  技术颜良  阅读(24)  评论(0编辑  收藏  举报