shellcontext是一个树形结构,最顶层就是context.Background(),这个context是初始的context,不能取消,用于派生后面的子context 而context.TODO()获得一个空context,类似context.Background(),但是这个在代码中代表一个暂不确定用法的占位 而生成context之前有下面的方式: 1. context.Background()或者context.TODO() : 获得根context,一般用于创建首个context的时候用 2. func WithValue(parent Context, key, val any) Context :创建带参键值对的context 3. func WithCancel(parent Context) (ctx Context, cancel CancelFunc) : 创建带cancel函数的,必须手动执行cancel 4. func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) : 创建带截至时间的,一般用于链式传播 5. func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) : 创建超时contxt,用于单任务,而如果链式传播使用这个,容易有误差,比如同时创建两个协程,都使用timeout,那么先创建的比后创建的总是快一点,使用deadline就不会有这种问题 6. func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc) 7. func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc) 6和7都是可以传递自定义的错误,比如超时context,如果超时了,通过下面例子展示:
gopackage main
import (
"context"
"fmt"
)
func main() {
ctx, cancel := context.WithCancelCause(context.Background())
err := fmt.Errorf("test error: %s", "my fault")
cancel(err)
fmt.Println(ctx.Err()) // 返回取消错误
fmt.Println(context.Cause(ctx)) // 返回自定义错误
}
PS D:\goProject\duping> go run .\main.go
context canceled
test error: my fault
goctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
cancel() // 手动取消
time.Sleep(6 * time.Second)
fmt.Println(ctx.Err()) // 输出什么?为什么?
输出:context canceled而不是context.deadlineExceeded
因为时间到了,上级context主动取消,时间到了导致的取消是context.deadlineExceeded
goctx := context.WithValue(
context.WithValue(context.Background(), "a", 1),
"a", 2,
)
fmt.Println(ctx.Value("a")) // 输出什么?
输出的是2,因为子context的参数覆盖了父context的参数
下面代码有什么问题?
gofunc handler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
result, err := doWork(ctx)
// 忘了 cancel()
if err != nil {
http.Error(w, err.Error(), 500)
return
}
fmt.Fprint(w, result)
}
没有defer cancel(),导致可能有遗留的协程占用资源
写一个函数:传入一组 URL,并发请求,2 秒超时,任意一个失败则取消全部,返回所有成功结果。
gopackage main
import (
"context"
"fmt"
"math/rand"
"sync"
"time"
)
var testURLs = []string{
"http://example.com",
"https://www.example.com",
"https://httpbin.org/get",
"https://httpbin.org/get?name=test&id=1",
"https://jsonplaceholder.typicode.com/posts/1",
"https://via.placeholder.com/300x200.png",
"https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
"http://httpbin.org/redirect-to?url=https://example.com",
"https://httpbin.org/status/404",
"https://httpbin.org/delay/2",
"wss://echo.websocket.org",
"https://httpbin.org/bytes/1048576",
"http://localhost:8080/api/health",
}
func DealUrl(url string) *response {
msg := &response{
url: url,
ok: rand.Intn(10000)%145 != 0,
}
return msg
}
type response struct {
url string
ok bool
}
func main() {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(2*time.Second))
defer cancel()
var wg sync.WaitGroup
result := make(chan *response)
worker := func(c context.Context, r chan<- *response) {
defer wg.Done()
url := c.Value("url")
urlstring := fmt.Sprintf("%v", url)
for {
msg := DealUrl(urlstring)
select {
case <-c.Done():
return
case r <- msg:
continue
}
}
}
for _, url := range testURLs {
wg.Add(1)
c := context.WithValue(ctx, "url", url)
go worker(c, result)
}
for {
msg, cok := <-result
fmt.Println(msg.url)
if !msg.ok && cok {
fmt.Println("not OK=======>", msg.url)
cancel()
break
}
}
wg.Wait()
}