轉載自:http://www.nljb.net/default/Golang%E4%B9%8BContext%E7%9A%84%E4%BD%BF%E7%94%A8/
簡介
在golang中的創建一個新的線程並不會返回像c語言類似的pid
所有我們不能從外部殺死某個線程,所有我就得讓它自己結束
之前我們用channel+select的方式,來解決這個問題
但是有些場景實現起來比較麻煩,例如由一個請求衍生出多個線程
並且之間需要滿足一定的約束關系,以實現一些諸如:
有效期,中止線程樹,傳遞請求全局變量之類的功能。
於是google 就為我們提供一個解決方案,開源了context包。
使用context包來實現上下文功能 .....
約定:需要在你的方法的傳入參數的第一個參數是context.Context的變量。
其實本身非常簡單,在導入這個包之后,初始化Context對象,在每個資源訪問方法中都調用它,然后在使用時檢查Context對象是否已經被Cancel,如果是就釋放綁定的資源
源碼剖析
context.Context 接口
- context包里的方法是線程安全的,可以被多個線程使用
- 當Context被canceled或是timeout, Done返回一個被closed 的channel
- 在Done的channel被closed后, Err代表被關閉的原因
- 如果存在, Deadline 返回Context將要關閉的時間
- 如果存在,Value 返回與 key 相關了的值,不存在返回 nil
// context 包的核心
type Context interface {
Done() <-chan struct{}
Err() error
Deadline() (deadline time.Time, ok bool)
Value(key interface{}) interface{}
}
我們不需要手動實現這個接口,context 包已經給我們提供了兩個
一個是 Background(),一個是 TODO()
這兩個函數都會返回一個Context的實例
只是返回的這兩個實例都是空 Context。
/*
TODO返回一個非空,空的上下文
在目前還不清楚要使用的上下文或尚不可用時
*/
context.TODO()
/*
Background返回一個非空,空的上下文。
這是沒有取消,沒有值,並且沒有期限。
它通常用於由主功能,初始化和測試,並作為輸入的頂層上下文
*/
context.Background()
主要方法
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key interface{}, val interface{}) Context
WithCancel 對應的是 cancelCtx ,其中,返回一個 cancelCtx ,同時返回一個 CancelFunc,CancelFunc 是 context 包中定義的一個函數類型:type CancelFunc func()。調用這個 CancelFunc 時,關閉對應的c.done,也就是讓他的后代goroutine退出
WithDeadline 和 WithTimeout 對應的是 timerCtx ,WithDeadline 和 WithTimeout 是相似的,WithDeadline 是設置具體的 deadline 時間,到達 deadline 的時候,后代 goroutine 退出,而 WithTimeout 簡單粗暴,直接 return WithDeadline(parent, time.Now().Add(timeout))
WithValue 對應 valueCtx ,WithValue 是在 Context 中設置一個 map,拿到這個 Context 以及它的后代的 goroutine 都可以拿到 map 里的值
context的創建
所有的context的父對象,也叫根對象,是一個空的context,它不能被取消,它沒有值,從不會被取消,也沒有超時時間,它常常作為處理request的頂層context存在,然后通過WithCancel、WithTimeout函數來創建子對象來獲得cancel、timeout的能力
當頂層的request請求函數結束后,我們就可以cancel掉某個context,從而通知別的routine結束
WithValue方法可以把鍵值對加入context中,讓不同的routine獲取
官方案例
// 在 handle 環境中使用
func handleSearch(w http.ResponseWriter, req *http.Request) {
// ctx is the Context for this handler. Calling cancel closes the
// ctx.Done channel, which is the cancellation signal for requests
// started by this handler.
var (
ctx context.Context
cancel context.CancelFunc
)
// 獲取參數 ...
timeout, err := time.ParseDuration(req.FormValue("timeout"))
if err == nil {
// The request has a timeout, so create a context that is
// canceled automatically when the timeout expires.
// 獲取成功, 則按照參數設置超時時間
ctx, cancel = context.WithTimeout(context.Background(), timeout)
} else {
// 獲取失敗, 則在該函數結束時結束 ...
ctx, cancel = context.WithCancel(context.Background())
}
// ----------------
// 這樣隨着cancel的執行,所有的線程都隨之結束了 ...
go A(ctx) +1
go B(ctx) +2
go C(ctx) +3
// ----------------
defer cancel() // Cancel ctx as soon as handleSearch returns.
}
// 監聽 ctx.Done() 結束 ...
func A(ctx context.Context) int {
// ... TODO
select {
case <-ctx.Done():
return -1
default:
// 沒有結束 ... 執行 ...
}
}
ctxhttp.go
package ctxhttp // import "golang.org/x/net/context/ctxhttp"
import (
"io"
"net/http"
"net/url"
"strings"
"golang.org/x/net/context"
)
// Do sends an HTTP request with the provided http.Client and returns
// an HTTP response.
//
// If the client is nil, http.DefaultClient is used.
//
// The provided ctx must be non-nil. If it is canceled or times out,
// ctx.Err() will be returned.
func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
if client == nil {
client = http.DefaultClient
}
resp, err := client.Do(req.WithContext(ctx))
// If we got an error, and the context has been canceled,
// the context's error is probably more useful.
if err != nil {
select {
case <-ctx.Done():
err = ctx.Err()
default:
}
}
return resp, err
}
// Get issues a GET request via the Do function.
func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
return Do(ctx, client, req)
}
// Head issues a HEAD request via the Do function.
func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
req, err := http.NewRequest("HEAD", url, nil)
if err != nil {
return nil, err
}
return Do(ctx, client, req)
}
// Post issues a POST request via the Do function.
func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequest("POST", url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", bodyType)
return Do(ctx, client, req)
}
// PostForm issues a POST request via the Do function.
func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) {
return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}
使用示例
package main
import (
"fmt"
"time"
"golang.org/x/net/context"
)
func Cdd(ctx context.Context) int {
fmt.Println(ctx.Value("NLJB"))
select {
// 結束時候做點什么 ...
case <-ctx.Done():
return -3
default:
// 沒有結束 ... 執行 ...
}
}
func Bdd(ctx context.Context) int {
fmt.Println(ctx.Value("HELLO"))
fmt.Println(ctx.Value("WROLD"))
ctx = context.WithValue(ctx, "NLJB", "NULIJIABEI")
go fmt.Println(Cdd(ctx))
select {
// 結束時候做點什么 ...
case <-ctx.Done():
return -2
default:
// 沒有結束 ... 執行 ...
}
}
func Add(ctx context.Context) int {
ctx = context.WithValue(ctx, "HELLO", "WROLD")
ctx = context.WithValue(ctx, "WROLD", "HELLO")
go fmt.Println(Bdd(ctx))
select {
// 結束時候做點什么 ...
case <-ctx.Done():
return -1
default:
// 沒有結束 ... 執行 ...
}
}
func main() {
// 自動取消(定時取消)
{
timeout := 3 * time.Second
ctx, _ := context.WithTimeout(context.Background(), timeout)
fmt.Println(Add(ctx))
}
// 手動取消
// {
// ctx, cancel := context.WithCancel(context.Background())
// go func() {
// time.Sleep(2 * time.Second)
// cancel() // 在調用處主動取消
// }()
// fmt.Println(Add(ctx))
// }
select {}
}
package main
import (
"fmt"
"time"
"golang.org/x/net/context"
)
// 模擬一個最小執行時間的阻塞函數
func inc(a int) int {
res := a + 1 // 雖然我只做了一次簡單的 +1 的運算,
time.Sleep(1 * time.Second) // 但是由於我的機器指令集中沒有這條指令,
// 所以在我執行了 1000000000 條機器指令, 續了 1s 之后, 我才終於得到結果。B)
return res
}
// 向外部提供的阻塞接口
// 計算 a + b, 注意 a, b 均不能為負
// 如果計算被中斷, 則返回 -1
func Add(ctx context.Context, a, b int) int {
res := 0
for i := 0; i < a; i++ {
res = inc(res)
select {
case <-ctx.Done():
return -1
default:
// 沒有結束 ... 執行 ...
}
}
for i := 0; i < b; i++ {
res = inc(res)
select {
case <-ctx.Done():
return -1
default:
// 沒有結束 ... 執行 ...
}
}
return res
}
func main() {
{
// 使用開放的 API 計算 a+b
a := 1
b := 2
timeout := 2 * time.Second
ctx, _ := context.WithTimeout(context.Background(), timeout)
res := Add(ctx, 1, 2)
fmt.Printf("Compute: %d+%d, result: %d\n", a, b, res)
}
{
// 手動取消
a := 1
b := 2
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(2 * time.Second)
cancel() // 在調用處主動取消
}()
res := Add(ctx, 1, 2)
fmt.Printf("Compute: %d+%d, result: %d\n", a, b, res)
}
}