Golang上下文context
上篇内容我们主要讲解了net/http标准库的使用,其中包含如何创建POST请求、GET请求以及如何携带参数的请求。
Context介绍
context释义为上下文,在我们使用goroutine时一般使用context来进行元数据的传递,非元数据不建议使用context来进行传递。那么我们主要是用context用来做什么呢?其实我们主要是是用来在多个goroutine中传递取消信号,调用链路信息等。
使用建议
-
不要在结构体中存储
context,将context类型作为函数的第一个参数,一般命名为ctx。 -
不要向函数中传递
nil的context,如果不知道使用那个可以使用TODO进行占位使用都一样。 -
不要将函数所需的非元数据塞到
context中。 -
同一个
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应该结束的时间,例如2s,ok表示是否有结束时间。 -
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说明:从上述运行结果我们可以看出,当调用
cancel时goroutine停止了。并打印了退出的原因(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的运行,这也是我们打印的结果为什么时3次goroutine持续运行中。
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说明:根据打印结果我们可以发现,其实
WithDeadline和WithTimeout差不多,只不过WithTimeout设置的是运行的时间,而WithDeadline表示goroutine运行到那个时间节点。

浙公网安备 33010602011771号