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
运行到那个时间节点。