Go语言学习第七课---context

一、初识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)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈小c

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值