一、初识context
文章目录
Go1.7加入了一个新的标准库context,它定义了Context类型,专门用来简化 对于处理单个请求的多个 goroutine 之间与请求域的数据、取消信号、截止时间等相关操作,这些操作可能涉及多个 API 调用。
对服务器传入的请求应该创建上下文,而对服务器的传出调用应该接受上下文。它们之间的函数调用链必须传递上下文,或者可以使用WithCancel、WithDeadline、WithTimeout或WithValue创建的派生上下文。当一个上下文被取消时,它派生的所有上下文也被取消。
二、context中的四个接口用法
2.1 WithCancel
WithCancel返回带有新Done通道的父节点的副本。当调用返回的cancel函数或当关闭父上下文的Done通道时,将关闭返回上下文的Done通道,无论先发生什么情况。
取消此上下文将释放与其关联的资源,因此代码应该在此上下文中运行的操作完成后立即调用cancel
示例:
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func worker(ctx context.Context) {
go worker02(ctx)
label:
for {
fmt.Println("worker")
time.Sleep(1 * time.Second)
select {
case <-ctx.Done():
break label
default:
}
}
defer wg.Done()
}
func worker02(ctx context.Context) {
label:
for {
fmt.Println("worker02")
time.Sleep(1 * time.Second)
select {
case <-ctx.Done():
break label
default:
}
}
defer wg.Done()
}
func main() {
ctx, concel := context.WithCancel(context.Background())
wg.Add(2)
go worker(ctx)
time.Sleep(3 * time.Second)
concel()
wg.Wait()
}
//worker
//worker02
//worker02
//worker
//worker
//worker02
//
//进程完成,并显示退出代码 0
2.2 WithDeadline
返回父上下文的副本,并将deadline调整为不迟于d。如果父上下文的deadline已经早于d,则WithDeadline(parent, d)在语义上等同于父上下文。当截止日过期时,当调用返回的cancel函数时,或者当父上下文的Done通道关闭时,返回上下文的Done通道将被关闭,以最先发生的情况为准。
取消此上下文将释放与其关联的资源,因此代码应该在此上下文中运行的操作完成后立即调用cancel。
package main
import (
"context"
"fmt"
"time"
)
func Worker03(ctx context.Context) {
Out:
for {
fmt.Println("hello")
time.Sleep(1 * time.Second)
select {
case <-ctx.Done():
fmt.Println("gorouting结束运行")
break Out
default:
}
}
}
func main(){
d := time.Now().Add(2 *time.Second)
contex,cancel := context.WithDeadline(context.Background(),d)
go Worker03(contex)
time.Sleep(10* time.Second)
cancel()
}
//hello
//hello
//gorouting结束运行
2.3 WithTimeout
注意,这里使用的WithTimeOut和WithDeadline作用和效果类似,只是传递的时间参数不同。
取消此上下文将释放与其相关的资源,因此代码应该在此上下文中运行的操作完成后立即调用cancel,通常用于数据库或者网络连接的超时控制。具体示例如下:
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func Worker03(ctx context.Context) {
Out:
for {
fmt.Println("hello")
time.Sleep(1 * time.Second)
select {
case <-ctx.Done():
fmt.Println("gorouting结束运行")
break Out
default:
}
}
wg.Done()
}
func main(){
contex,cancel := context.WithTimeout(context.Background(),2 *time.Second)
wg.Add(1)
go Worker03(contex)
cancel()
wg.Wait()
}
//hello
//gorouting结束运行
2.4 WithValue
示例:通过withvalue来计算函数的执行时间
package main
import (
"context"
"fmt"
"sync"
"time"
)
type TraceCode string
var wg sync.WaitGroup
func func1(ctx context.Context) {
key := TraceCode("ElapsedTime")
ElapsedTime, ok := ctx.Value(key).(time.Time)
if !ok {
fmt.Println("ElapsedTime err")
}
func1time := time.Now()
fmt.Println("main 函数消耗时间", func1time.Sub(ElapsedTime))
Out:
for {
select {
case <-ctx.Done():
break Out
default:
}
}
defer wg.Done()
fmt.Println("函数fun1结束")
time.Sleep(time.Microsecond * 50)
ctx = context.WithValue(ctx, TraceCode("ElapsedTime"), func1time)
func2(ctx)
}
func func2(ctx context.Context) {
key := TraceCode("ElapsedTime")
ElapsedTime, ok := ctx.Value(key).(time.Time)
if !ok {
fmt.Println("ElapsedTime err")
}
func2time := time.Now()
fmt.Println("fun1 函数消耗时间", func2time.Sub(ElapsedTime))
defer wg.Done()
fmt.Println("函数fun2结束")
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
maintime := time.Now()
time.Sleep(1 * time.Second)
ctx = context.WithValue(ctx, TraceCode("ElapsedTime"), maintime)
wg.Add(2)
go func1(ctx)
wg.Wait()
defer cancel()
}
三、案例:限制请求接口的超时时间
server端
package main
import (
"fmt"
"math/rand"
"net/http"
"time"
)
// server端,随机出现慢响应
func indexHandler(w http.ResponseWriter, r *http.Request) {
number := rand.Intn(2)
if number == 0 {
time.Sleep(time.Second * 10) // 耗时10秒的慢响应
fmt.Fprintf(w, "slow response")
return
}
fmt.Fprint(w, "quick response")
}
func main() {
http.HandleFunc("/", indexHandler)
err := http.ListenAndServe(":8000", nil)
if err != nil {
panic(err)
}
}
client端
// context_timeout/client/main.go
package main
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
)
// 客户端
type respData struct {
resp *http.Response
err error
}
func doCall(ctx context.Context) {
transport := http.Transport{
// 请求频繁可定义全局的client对象并启用长链接
// 请求不频繁使用短链接
DisableKeepAlives: true, }
client := http.Client{
Transport: &transport,
}
respChan := make(chan *respData, 1)
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
if err != nil {
fmt.Printf("new requestg failed, err:%v\n", err)
return
}
req = req.WithContext(ctx) // 使用带超时的ctx创建一个新的client request
var wg sync.WaitGroup
wg.Add(1)
defer wg.Wait()
go func() {
resp, err := client.Do(req)
fmt.Printf("client.do resp:%v, err:%v\n", resp, err)
rd := &respData{
resp: resp,
err: err,
}
respChan <- rd
wg.Done()
}()
select {
case <-ctx.Done():
//transport.CancelRequest(req)
fmt.Println("call api timeout")
case result := <-respChan:
fmt.Println("call server api success")
if result.err != nil {
fmt.Printf("call server api failed, err:%v\n", result.err)
return
}
defer result.resp.Body.Close()
data, _ := ioutil.ReadAll(result.resp.Body)
fmt.Printf("resp:%v\n", string(data))
}
}
func main() {
// 定义一个100毫秒的超时
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100)
defer cancel() // 调用cancel释放子goroutine资源
doCall(ctx)
}