golang基础-网络请求WithTimeout、上下文withValue、withCancel、WithDeadline

在 Go http包的Server中,每一个请求在都有一个对应的 goroutine 去处理。请求处理函数通常会启动额外的 goroutine 用来访问后端服务,比如数据库和RPC服务。用来处理一个请求的 goroutine 通常需要访问一些与请求特定的数据,比如终端用户的身份认证信息、验证相关的token、请求的截止时间。 当一个请求被取消或超时时,所有用来处理该请求的 goroutine 都应该迅速退出,然后系统才能释放这些 goroutine 占用的资源。

在Google 内部,开发了 Context 包,专门用来简化 对于处理单个请求的多个 goroutine 之间与请求域的数据、取消信号、截止时间等相关操作,这些操作可能涉及多个 API 调用。你可以通过 go get golang.org/x/net/context 命令获取这个包。本文要讲的就是如果使用这个包,同时也会提供一个完整的例子。

Context interface

网络请求超时控制
type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}
Deadline()返回一个time.Time,是当前 Context 的应该结束的时间,ok 表示是否有 deadline
Done()返回一个struct{}类型的只读 channel
Err()返回 Context 被取消时的错误
Value(key interface{}) 是 Context 自带的 K-V 存储功能
package main

import (
    "context"
    "fmt"
    "io/ioutil"
    "net/http"
    "time"
)
type Result struct {
    r   *http.Response
    err error
}
func process() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    //释放资源
    defer cancel()
    tr := &http.Transport{}
    client := &http.Client{Transport: tr}
    resultChan := make(chan Result, 1)
    //发起请求
    req, err := http.NewRequest("GET", "http://www.baidu.com", nil)
    if err != nil {
        fmt.Println("http request failed, err:", err)
        return
    }
    /*
    func (c *Client) Do(req *Request) (*Response, error)
    */
    go func() {
        resp, err := client.Do(req)
        pack := Result{r: resp, err: err}
        //将返回信息写入管道(正确或者错误的)
        resultChan <- pack
    }()
    select {
    case <-ctx.Done():
        tr.CancelRequest(req)
        er:= <-resultChan
        fmt.Println("Timeout!",er.err)
    case res := <-resultChan:
        defer res.r.Body.Close()
        out, _ := ioutil.ReadAll(res.r.Body)
        fmt.Printf("Server Response: %s", out)
    }
    return
}
func main() {
    process()
}

输出如下:
这里写图片描述

如果修改下代码

req, err := http.NewRequest("GET", "http://google.com", nil)

请求超时,输出log信息如下

PS E:\golang\go_pro\src\safly> go build main.go
PS E:\golang\go_pro\src\safly> main.exe
Timeout! Get http://google.com: net/http: request canceled while waiting for connection
PS E:\golang\go_pro\src\safly>
上下文WithValue
package main

import (
    "context"
    "fmt"
)

func process(ctx context.Context) {
    ret,ok := ctx.Value("trace_id").(int)
    if !ok {
        ret = 21342423
    }

    fmt.Printf("ret:%d\n", ret)

    s , _ := ctx.Value("session").(string)
    fmt.Printf("session:%s\n", s)
}

func main() {
    ctx := context.WithValue(context.Background(), "trace_id", 13483434)
    ctx = context.WithValue(ctx, "session", "sdlkfjkaslfsalfsafjalskfj")
    process(ctx)
}

输出如下:

PS E:\golang\go_pro\src\safly> go run main.go
ret:13483434
session:sdlkfjkaslfsalfsafjalskfj
PS E:\golang\go_pro\src\safly>
超时控制WithDeadline
package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    d := time.Now().Add(4 * time.Second)
    //50毫秒到了,触发如下代码
    ctx, cancel := context.WithDeadline(context.Background(), d)


    defer cancel()

    select {
    case <-time.After(3 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        //50毫秒到了,执行该代码
        fmt.Println(ctx.Err())
    }

}

输出如下:

PS E:\golang\go_pro\src\safly> go run deadline.go
overslept
PS E:\golang\go_pro\src\safly>

如果将上面代码修改为

func main() {
    d := time.Now().Add(4 * time.Second)
    //50毫秒到了,触发如下代码
    ctx, cancel := context.WithDeadline(context.Background(), d)


    defer cancel()

    select {
    case <-time.After(5 * time.Second):
        fmt.Println("overslept")
    case <-ctx.Done():
        //50毫秒到了,执行该代码
        fmt.Println(ctx.Err())
    }

}

然后在执行输出如下:

PS E:\golang\go_pro\src\safly> go run deadline.go
context deadline exceeded
PS E:\golang\go_pro\src\safly>
WithCancel

我们来了解一个利用context结束goroutine的demo

package main

import (
    "context"
    "fmt"
    "time"
)

/*
 创建一个管道chan,启动goroutine
 for循环存数据
**/
func gen(ctx context.Context) <-chan int {
    dst := make(chan int)
    n := 1
    go func() {
        for {
            select {
            case <-ctx.Done():
                //执行defer cancel操作后,就会执行到该select入库
                fmt.Println("i exited")
                return // returning not to leak the goroutine
            case dst <- n:
                n++
            }
        }
    }()
    return dst
}

func test() {

    ctx, cancel := context.WithCancel(context.Background())
    //当取数据n == 5时候,执行defer cancel操作
    defer cancel() 
    intChan := gen(ctx)
    for n := range intChan {
        fmt.Println(n)
        if n == 5 {
            break
        }
    }
}
func main() {
    test()
    time.Sleep(time.Hour)
}

输出如下:

PS E:\golang\go_pro\src\safly> go run cancle.go
1
2
3
4
5
i exited
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值