context接口定义
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
context特性描述
1、进入的请求应该创建一个上下文,输出应该接收一个上下文
2、功能调用链应该传播上下文
3、调用CancelFunc,
会取消子上下文和它的派生上下文,
删除父对子的引用
停止相关的定时器
3、调用 CancelFunc 失败会泄漏孩子及其孩子,直到父母被取消或计时器触发
4、go vet 可以检查所有的控制流路径是否使用了CancelFunc
./c.go:85:6: the cancel function returned by context.WithTimeout should be called, not discarded, to avoid a context leak
5、不要将上下文存储在结构类型中,而是将上下文显式传递给需要它的每个函数
6、上下文应该为第一个参数,通常命名为ctx
7、不要传递 nil 上下文,即使函数允许传递 context.TODO 如果您不确定要使用哪个上下文。
context.TODO()
8、仅将上下文值用于传输流程和API边界的请求范围数据,而不用于将可选参数传递给函数。
9、同一个上下文在多个不同的协程中使用是合理的,而且是安全的
context接口方法说明
Deadline() (deadline time.Time, ok bool)
返回上下文应该被取消的时间
c := context.Background()
fmt.Println(time.Now())
c, cancel := context.WithTimeout(c, time.Second*2)
t, ok := c.Deadline()
fmt.Println(t, ok)
fmt.Println(<-c.Done()) // 2s到之前,一直被阻塞
fmt.Println(time.Now())
Output:
2022-06-08 08:28:38.617696 +0800 CST m=+0.000180371
2022-06-08 08:28:40.617885 +0800 CST m=+2.000369547 true
2022-06-08 08:28:40.618983 +0800 CST m=+2.001474187
Done() <-chan struct{}
返回一个被关闭的通道,代表上下文被取消了 如果上下文永远不会被取消,返回 nil
c := context.Background()
fmt.Println(c.Done()) // <nil>
Err() error
Done未关闭,返回nil Done已关闭,返回错误原因
方式1:
c := context.Background()
c, cancel := context.WithTimeout(c, time.Second*2)
fmt.Println(c.Err()) // nil
con(cancel)
fmt.Println(c.Err()) // context canceled
fmt.Println(<-c.Done()) // {}
fmt.Println(c.Err()) // context canceled
方式2:
c := context.Background()
c, cancel := context.WithTimeout(c, time.Second*2)
// c, cancel := context.WithDeadline(c, time.Now().Add(time.Second*2)) // 和WithTimeout效果相同
defer cancel()
fmt.Println(<-c.Done()) // {}
fmt.Println(c.Err()) // context deadline exceeded
func con(ctx context.CancelFunc) {
defer func() {
ctx()
}()
}
Value(key any) any
仅将上下文值用于传输流程和API边界的请求范围数据,而不用于将可选参数传递给函数。
key标识上下文中的特定值。 希望在 Context 中存储值的函数通常会在全局变量中分配一个键,然后将该键用作 context 的参数。 WithValue 和 Context.Value 。 键可以是任何支持相等的类型; 包应将键定义为未导出的类型以避免冲突
定义 Context 键的包应该为使用该键存储的值提供类型安全的访问器
package user
import "context"
// User 是存储在上下文中的值的类型。
type User struct {...}
// key 是此包中定义的键的未导出类型。 这可以防止与其他包中定义的键发生冲突。
type key int
// userKey 是 Contexts 中 user.User 值的键。 未导出; 客户端使用 user.NewContext 和
// user.FromContext 而不是直接使用这个键。
var userKey key
// NewContext 返回一个带有值 u 的新 Context
func NewContext(ctx context.Context, u *User) context.Context {
return context.WithValue(ctx, userKey, u)
}
// FromContext 返回存储在 ctx 中的用户值(如果有)
func FromContext(ctx context.Context) (*User, bool) {
u, ok := ctx.Value(userKey).(*User)
return u, ok
}
context使用最佳实践 (官方userip的例子)并发安全的上下文在多个包中传递的最佳实践方式
package google
import (
"context"
"ctx/userip"
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
)
type Results []Result
type Result struct {
Title, URL string
}
func Search(ctx context.Context, query string) (Results, error) {
req, err := http.NewRequest("GET", "https://ajax.googleapis.com/ajax/services/search/web?v=1.0", nil)
if err != nil {
return nil, err
}
q := req.URL.Query()
q.Set("q", query)
if userIP, ok := userip.FromContext(ctx); ok {
q.Set("userip", userIP.String())
}
req.URL.RawQuery = q.Encode()
var results Results
err = httpDo(ctx, req, func(resp *http.Response, err error) error {
if err != nil {
return err
}
defer resp.Body.Close()
var data struct {
ResponseData struct {
Results []struct {
TitleNoFormatting, URL string
}
}
}
if 200 != resp.StatusCode {
return errors.New("Deal Fail, HttpCode:" + strconv.FormatInt(int64(resp.StatusCode), 10))
}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
fmt.Println("some error:", err.Error())
return err
}
for _, res := range data.ResponseData.Results {
results = append(results, Result{Title: res.TitleNoFormatting, URL: res.URL})
}
return nil
})
return results, err
}
func httpDo(ctx context.Context, r *http.Request, f func(*http.Response, error) error) error {
c := make(chan error, 1)
r = r.WithContext(ctx)
go func() {
c <- f(http.DefaultClient.Do(r))
}()
select {
case <-ctx.Done():
<-c
return ctx.Err()
case err := <-c:
return err
}
}
package userip
import (
"context"
"fmt"
"net"
"net/http"
)
func FromRequest(req *http.Request) (net.IP, error) {
ip, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
}
userIP := net.ParseIP(ip)
if userIP == nil {
return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
}
return userIP, nil
}
// 不导出,防止和其他包的类型名冲突
type key int
const userIPKey key = 0
// 包向context中添加值的官方推荐方式,不是操作上下文,而是派生新的上下文
func NewContext(ctx context.Context, UserIP net.IP) context.Context {
return context.WithValue(ctx, userIPKey, UserIP)
}
// 包从context中查询值的推荐方式,不是直接操作,而是封装方法来获取
func FromContext(ctx context.Context) (net.IP, bool) {
userIP, ok := ctx.Value(userIPKey).(net.IP)
return userIP, ok
}
package main
import (
"context"
"ctx/google"
"ctx/userip"
"html/template"
"log"
"net/http"
"time"
)
var resultsTemplate = template.Must(template.New("results").Parse(`
<html>
<head/>
<body>
<ol>
{{range .Results}}
<li>{{.Title}} - <a href="{{.URL}}">{{.URL}}</a></li>
{{end}}
</ol>
<p>{{len .Results}} results in {{.Elapsed}}; timeout {{.Timeout}}</p>
</body>
</html>
`))
func main() {
http.HandleFunc("/search", handleSearch)
log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
}
func handleSearch(w http.ResponseWriter, req *http.Request) {
var (
ctx context.Context
cancel context.CancelFunc
)
timeout, err := time.ParseDuration(req.FormValue("timeout"))
if err == nil {
ctx, cancel = context.WithTimeout(context.Background(), timeout)
} else {
ctx, cancel = context.WithCancel(context.Background())
}
defer cancel()
query := req.FormValue("q")
if query == "" {
http.Error(w, "no query", http.StatusBadRequest)
return
}
userIP, err := userip.FromRequest(req)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
ctx = userip.NewContext(ctx, userIP)
start := time.Now()
results, err := google.Search(ctx, query)
elapsed := time.Since(start)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := resultsTemplate.Execute(w, struct {
Results google.Results
Timeout, Elapsed time.Duration
}{
Results: results,
Timeout: timeout,
Elapsed: elapsed,
}); err != nil {
log.Print(err)
return
}
}