之前有兄弟留言想学习一下 Context
,他来了,虽迟但到。
前言
在 Go 语言中,Context 是一个十分重要的概念,它用于在不同的 goroutine 之间传递申请域的相干数据,并且能够用来管制 goroutine 的生命周期和勾销操作。本文将深入探讨 Go 语言中 Context 个性 和 Context 的高级应用办法。
根本用法
在 Go 语言中,Context 被定义为一个接口类型,它蕴含了三个办法:
# go version 1.18.10
type Context interface {Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
- Deadline() 办法用于获取 Context 的截止工夫,
- Done() 办法用于返回一个只读的 channel,用于告诉以后 Context 是否曾经被勾销,
- Err() 办法用于获取 Context 勾销的起因,
- Value() 办法用于获取 Context 中保留的键值对数据。
咱们日常编写代码时,Context 对象会被被约定作为函数的第一个参数传递,eg:
func users(ctx context.Context, request *Request) {// ... code}
在函数中,能够通过 ctx 参数来获取相干的 Context 数据,举个超时的 eg:
deadline, ok := ctx.Deadline()
if ok && deadline.Before(time.Now()) {
// 超时
return
}
Context 管制 goroutine 的生命周期
在 Go 语言中,goroutine 是一种十分常见的并发编程模型,而 Context 能够被用来管制 goroutine 的生命周期,从而避免出现 goroutine 透露或者不必要的期待操作。
eg,看一下下方代码:
func users(ctx context.Context, req *Request) {
// 启动一个 goroutine 来解决申请
go func() {// 解决申请...}()}
下面的代码中,咱们启动了一个 goroutine 来解决申请,然而没有任何形式来管制这个 goroutine 的生命周期,如果这个申请被勾销了,那么这个 goroutine 就会始终存在,直到它实现为止。为了防止这种状况的产生,咱们能够应用 Context 来管制 goroutine 的生命周期,eg:
func users(ctx context.Context, req *Request) {
// 启动一个 goroutine 来解决申请
go func(ctx context.Context) {// 解决申请...}(ctx)
}
在下面的代码中,咱们将 Context 对象作为参数传递给了 goroutine 函数,这样在申请被勾销时,goroutine 就能够及时退出。
应用 WithValue() 传递数据
除了用于管制 goroutine 的生命周期,Context 还能够被用来在不同的 goroutine 之间传递申请域的相干数据。为了实现这个目标,咱们能够应用 Context 的 WithValue() 办法,eg:
type key int
const (userKey key = iota)
func users(ctx context.Context, req *Request) {
// 从申请中获取用户信息
user := req.GetUser
// 将用户信息保留到 Context 中
ctx = context.WithValue(ctx, userKey, user)
// 启动一个 goroutine 来解决申请
go func(ctx context.Context) {
// 从 Context 中获取用户信息
user := ctx.Value(userKey).(*User)
// 解决申请...
}(ctx)
}
在下面的代码中,咱们定义了一个 key
类型的常量 userKey
,而后在 users()
函数中将用户信息保留到了 Context 中,并将 Context 对象传递给了 goroutine 函数。
在 goroutine 函数中,咱们应用 ctx.Value()
办法来获取 Context 中保留的用户信息。
注:
Context 中保留的键值对数据应该是线程平安的,因为它们可能会在多个 goroutine 中同时拜访。
应用 WithCancel() 勾销操作
除了管制 goroutine 的生命周期和传递数据之外,Context 还能够被用来执行勾销操作。为了实现这个目标,咱们能够应用 Context 的 WithCancel()
办法,eg:
func users(ctx context.Context, req *Request) {
// 创立一个能够勾销的 Context 对象
ctx, cancel := context.WithCancel(ctx)
// 启动一个 goroutine 来解决申请
go func(ctx context.Context) {
// 期待申请实现或者被勾销
select {case <-time.After(time.Second):
// 申请实现
fmt.Println("Request finish")
case <-ctx.Done():
// 申请被勾销
fmt.Println("Request canceled")
}
}(ctx)
// 期待一段时间后勾销申请
time.Sleep(time.Millisecond * 800)
cancel()}
在下面的代码中,咱们应用 WithCancel() 办法创立了一个能够勾销的 Context 对象,并将勾销操作封装在了一个 cancel() 函数中。而后咱们启动了一个 goroutine 函数,应用 select 语句期待申请实现或者被勾销,最初在主函数中期待一段时间后调用 cancel() 函数来勾销申请。
应用 WithDeadline() 设置截止工夫
除了应用 WithCancel() 办法进行勾销操作之外,Context 还能够被用来设置截止工夫,以便在超时的状况下勾销申请。为了实现这个目标,咱们能够应用 Context 的 WithDeadline() 办法,eg:
func users(ctx context.Context, req *Request) {
// 设置申请的截止工夫为以后工夫加上 1 秒钟
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second))
// 启动一个 goroutine 来解决申请
go func(ctx context.Context) {
// 期待申请实现或者超时
select {case <-time.After(time.Millisecond * 500):
// 申请实现
fmt.Println("Request finish")
case <-ctx.Done():
// 申请超时或者被勾销
fmt.Println("Request canceled or timed out")
}
}(ctx)
// 期待一段时间后勾销申请
time.Sleep(time.Millisecond * 1500)
cancel()}
在下面的代码中,咱们应用 WithDeadline() 办法设置了一个截止工夫为以后工夫加上 1 秒钟的 Context 对象,并将超时操作封装在了一个 cancel() 函数中。而后咱们启动了一个 goroutine 函数,应用 select 语句期待申请实现或者超时,最初在主函数中期待一段时间后调用 cancel() 函数来勾销申请。
注:
在应用 WithDeadline() 办法设置截止工夫的时候,如果截止工夫曾经过期,则 Context 对象将被立刻勾销。
应用 WithTimeout() 设置超时工夫
除了应用 WithDeadline() 办法进行截止工夫设置之外,Context 还能够被用来设置超时工夫。为了实现这个目标,咱们能够应用 Context 的 WithTimeout() 办法,eg:
func users(ctx context.Context, req *Request) {
// 设置申请的超时工夫为 1 秒钟
ctx, cancel := context.WithTimeout(ctx, time.Second)
// 启动一个 goroutine 来解决申请
go func(ctx context.Context) {
// 期待申请实现或者超时
select {case <-time.After(time.Millisecond * 500):
// 申请实现
fmt.Println("Request completed")
case <-ctx.Done():
// 申请超时或者被勾销
fmt.Println("Request canceled or timed out")
}
}(ctx)
// 期待一段时间后勾销申请
time.Sleep(time.Millisecond * 1500)
cancel()}
在下面的代码中,咱们应用 WithTimeout() 办法设置了一个超时工夫为 1 秒钟的 Context 对象,并将超时操作封装在了一个 cancel() 函数中。而后咱们启动了一个 goroutine 函数,应用 select 语句期待申请实现或者超时,最初在主函数中期待一段时间后调用 cancel() 函数来勾销申请。
注:
须要留神的是,在应用 WithTimeout() 办法设置超时工夫的时候,如果超时工夫曾经过期,则 Context 对象将被立刻勾销。
Context 的传递
在一个应用程序中,不同的 goroutine 可能须要共享同一个 Context 对象。为了实现这个目标,Context 对象能够通过函数调用或者网络传输等形式进行传递。
例如,咱们能够在一个解决 HTTP 申请的函数中创立一个 Context 对象,并将它作为参数传递给一个数据库查问函数,以便在查问过程中应用这个 Context 对象进行勾销操作。代码 eg:
func users(ctx context.Context, req *Request) {
// 在解决 HTTP 申请的函数中创立 Context 对象
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
// 调用数据库查问函数并传递 Context 对象
result, err := findUserByName(ctx, req)
if err != nil {// 解决查问谬误...}
// 解决查问后果...
}
func findUserByName(ctx context.Context, req *Request) (*Result, error) {
// 在数据库查问函数中应用传递的 Context 对象
rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE name = ?", req.Name)
if err != nil {// 解决查问谬误...}
defer rows.Close()
// 解决查问后果...
}
在下面的代码中,咱们在解决 HTTP 申请的函数中创立了一个 Context 对象,并将它作为参数传递给了一个数据库查问函数 findUserByName()
。在 findUserByName()
函数中,咱们应用传递的 Context 对象来调用 db.QueryContext()
办法进行查问操作。因为传递的 Context 对象可能会在查问过程中被勾销,因而咱们须要在查问实现后查看查问操作的谬误,以便进行相应的解决。
注:
在进行 Context 的传递时,咱们须要保障传递的 Context 对象是原始 Context 对象的子 Context,以便在须要勾销操作时可能同时勾销所有相干的 goroutine。如果传递的 Context 对象不是原始 Context 对象的子 Context,则勾销操作只会影响到以后 goroutine,而无奈勾销其余相干的 goroutine。
总结
在 Go 语言中,Context 是一个十分重要的个性,包含其根本用法和一些高级用法。Context 能够用于治理 goroutine 的生命周期和勾销操作,避免出现资源透露和死锁等问题,同时也能够进步应用程序的性能和可维护性。
在应用 Context 的时候, 须要留神以下几点:
-
- 在创立 goroutine 时,须要将原始 Context 对象作为参数传递给它。
-
- 在 goroutine 中,须要应用传递的 Context 对象来进行勾销操作,以便可能及时开释相干的资源。
-
- Context 的传递时,须要保障传递的 Context 对象是原始 Context 对象的子 Context,以便在须要勾销操作时可能同时勾销所有相干的 goroutine。
-
- 在应用 WithCancel 和 WithTimeout 办法创立 Context 对象时,须要及时调用 cancel 函数,以便可能及时开释资源。
-
- 在一些场景下,能够应用 WithValue 办法将数据存储到 Context 中,以便在不同的 goroutine 之间共享数据。