Context专门用来简化 对于处理单个请求的多个 goroutine 之间与请求域的数据、取消信号、截止时间等相关操作,这些操作可能涉及多个 API 调用。
context.Context是一个接口,该接口定义了四个需要实现的方法
其中:
Deadline
方法需要返回当前Context
被取消的时间,也就是完成工作的截止时间(deadline);Done
方法需要返回一个Channel
,这个Channel会在当前工作完成或者上下文被取消之后关闭,多次调用Done
方法会返回同一个Channel;Context
被取消就会返回Canceled
错误;Context
超时就会返回DeadlineExceeded
错误;Value
方法会从Context
中返回键对应的值,对于同一个上下文来说,多次调用Value
并传入相同的Key
会返回相同的结果,该方法仅用于传递跨API和进程间跟请求域的数据;context 的一个常见用途是传递请求域的数据。例如,我们可以通过 context.WithValue 将一些数据附加到 Context 中,并在后续的函数调用中通过 Value 方法获取这些数据。
package main
import (
"context"
"fmt"
)
func main() {
ctx := context.Background()
ctx = context.WithValue(ctx, "name", "dgg")
GetUser(ctx)
}
func GetUser(ctx context.Context) {
fmt.Println(ctx.Value("name"))
}
## 取消协程 WithCancel
```go
package main
import (
"context"
"fmt"
"sync"
"time"
)
// 两秒钟停掉
var wg sync.WaitGroup
func main() {
t1 := time.Now()
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go func() {
ip, err := GetIp1(ctx)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ip)
}()
go func() {
time.Sleep(2 * time.Second)
//取消协程
cancel()
wg.Done()
}()
wg.Wait()
fmt.Println("执行完成", time.Since(t1))
}
func GetIp1(ctx context.Context) (ip string, err error) {
select {
case <-ctx.Done():
fmt.Println("协程取消", ctx.Err())
return "", ctx.Err()
case <-time.After(4 * time.Second):
ip = "11111"
return ip, nil
}
}
在这个例子中,我们通过 context.WithValue 将 “name” 键对应的值 “dgg” 附加到 Context 中,然后在 GetUser 函数中通过 ctx.Value(“name”) 获取这个值。
context.WithCancel 是一个非常有用的函数,它允许我们创建一个可以被取消的 Context。当调用 cancel 函数时,Context 会被取消,Done 方法返回的 Channel 会被关闭。
package main
import (
"context"
"fmt"
"sync"
"time"
)
// 两秒钟停掉
var wg sync.WaitGroup
func main() {
t1 := time.Now()
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go func() {
ip, err := GetIp1(ctx)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ip)
}()
go func() {
time.Sleep(2 * time.Second)
//取消协程
cancel()
wg.Done()
}()
wg.Wait()
fmt.Println("执行完成", time.Since(t1))
}
func GetIp1(ctx context.Context) (ip string, err error) {
select {
case <-ctx.Done():
fmt.Println("协程取消", ctx.Err())
return "", ctx.Err()
case <-time.After(4 * time.Second):
ip = "11111"
return ip, nil
}
}
在这个例子中,我们创建了一个可以被取消的 Context,并在另一个 goroutine 中调用了 cancel 函数。当 cancel 函数被调用时,GetIp1 函数会收到取消信号,并立即返回错误。
context.WithDeadline 允许我们为 Context 设置一个截止时间。当截止时间到达时,Context 会被自动取消。
package main
import (
"context"
"fmt"
"sync"
"time"
)
// 两秒钟停掉
var wg1 sync.WaitGroup
func main() {
t1 := time.Now()
ctx, _ := context.WithDeadline(context.Background(), time.Now().Add(2*time.Second))
wg1.Add(1)
go func() {
ip, err := GetIp2(ctx)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ip)
}()
wg1.Wait()
fmt.Println("执行完成", time.Since(t1))
}
func GetIp2(ctx context.Context) (ip string, err error) {
go func() {
select {
case <-ctx.Done():
fmt.Println("协程取消", ctx.Err())
err = ctx.Err()
wg1.Done()
return
}
}()
time.Sleep(4 * time.Second)
ip = "11111"
wg1.Done()
return
}
在这个例子中,我们为 Context 设置了一个截止时间,当截止时间到达时,GetIp2 函数会收到取消信号,并立即返回错误。
context.WithTimeout 是 WithDeadline 的一个特例,它允许我们为 Context 设置一个超时时间。当超时时间到达时,Context 会被自动取消。
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg2 sync.WaitGroup
func main() {
t1 := time.Now()
// 使用 WithTimeout 设置超时时间为 2 秒
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // 确保在函数退出时调用 cancel 函数[^1^]
wg2.Add(1)
go func() {
defer wg2.Done()
ip, err := GetIp3(ctx)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(ip)
}()
wg2.Wait()
fmt.Println("执行完成", time.Since(t1))
}
func GetIp3(ctx context.Context) (ip string, err error) {
// 使用 select 语句监听 ctx.Done() 和 time.After()
select {
case <-ctx.Done():
fmt.Println("协程取消", ctx.Err())
return "", ctx.Err() // 如果上下文被取消,立即返回错误[^1^]
case <-time.After(4 * time.Second):
ip = "11111"
return ip, nil
}
}
在这个例子中,我们为 Context 设置了一个超时时间,当超时时间到达时,GetIp3 函数会收到取消信号,并立即返回错误。
context 包是Go语言并发编程中的一个重要工具。它通过提供 WithValue、WithCancel、WithDeadline 和 WithTimeout 等函数,让我们可以优雅地处理并发任务中的数据传递、取消信号和超时问题。在实际开发中,合理使用 context 可以让我们的代码更加简洁、高效和健壮。