1. 程式人生 > >golang基礎-網路請求WithTimeout、上下文withValue、withCancel、WithDeadline

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