Go標準ライブラリContect
58953 ワード
Go標準ライブラリContect
Go httpパッケージのServerでは、それぞれの要求が対応するgoroutineで処理されます。要求処理関数は、通常、データベースやRPCサービスなどのバックエンドサービスにアクセスするために追加のgoroutineを起動する。要求を処理するためのgoroutineは、一般に、端末ユーザのアイデンティティ認証情報、認証に関するtoken、要求の締め切り時間など、特定のデータにアクセスする必要がある。一つの要求がキャンセルされたり、オーバータイムされたりした場合、要求を処理するためのすべてのgoroutineは速やかに終了し、その後、システムはこれらのgoroutineが占有するリソースを解放することができる。
なぜContectが必要ですか?
基本例
Go 1.7は、単一の要求を処理するための複数のgoroutineと要求ドメインのデータ、キャンセル信号、締切時間などの関連動作を単純化するための
サーバからの要求はコンテキストを作成し、サーバへの呼び出しはコンテキストを受け入れるべきです。これらの間の関数コールチェーンは、コンテキストを伝達しなければならない、または
コネクタ 現在 現在 Background()とTODO()
Goは、2つの関数を内蔵する。
Withシリーズ関数
さらに、
WithCacel
このコンテキストをキャンセルすると、関連するリソースが解放されますので、コードはこのコンテキストで実行されるべき操作が完了したら、すぐにキャンセルを呼び出すべきです。
WithDeadline
このコンテキストをキャンセルすると、関連するリソースが解放されますので、コードはこのコンテキストで実行されるべき操作が完了したら、すぐにキャンセルを呼び出すべきです。
WithTimeout
このコンテキストをキャンセルすると、関連するリソースが解放されますので、コードはこのコンテキストで実行されるべき操作が完了したら、すぐにキャンセルを呼び出します。通常はデータベースまたはネットワーク接続のタイムアウト制御に使用されます。具体例は以下の通りです。
APIとプロセス間の要求領域を伝達するデータのみに、コンテキスト値を使用して、オプションのパラメータを関数に伝達するために使用されるのではない。
提供されたキーは比較可能であり、コンテキストを使用してパケット間で衝突が起こることを避けるために、転送Contact をパラメータで表示することを推奨します。は、Contectをパラメータの関数とする方法であり、Contectを最初のパラメータとするべきである。 関数メソッドにContectを渡す時は、nilを伝えないでください。何を伝えたらいいのか分からない時は、context.TODO() を使います。 ContectのValue関連方法は、要求ドメインの必要なデータを伝達し、オプションパラメータ を転送するために使用されるべきではない。 Contectはスレッドの安全性であり、複数のgoroutineの中で安心して を伝えることができます。
クライアントタイムアウトキャンセル例
Go httpパッケージのServerでは、それぞれの要求が対応するgoroutineで処理されます。要求処理関数は、通常、データベースやRPCサービスなどのバックエンドサービスにアクセスするために追加のgoroutineを起動する。要求を処理するためのgoroutineは、一般に、端末ユーザのアイデンティティ認証情報、認証に関するtoken、要求の締め切り時間など、特定のデータにアクセスする必要がある。一つの要求がキャンセルされたり、オーバータイムされたりした場合、要求を処理するためのすべてのgoroutineは速やかに終了し、その後、システムはこれらのgoroutineが占有するリソースを解放することができる。
なぜContectが必要ですか?
基本例
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
//
func worker() {
for {
fmt.Println("worker")
time.Sleep(time.Second)
}
//
wg.Done()
}
func main() {
wg.Add(1)
go worker()
// goroutine
wg.Wait()
fmt.Println("over")
}
グローバル変数方式package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
var exit bool
// :
// 1.
// 2. worker goroutine, 。
func worker() {
for {
fmt.Println("worker")
time.Sleep(time.Second)
if exit {
break
}
}
wg.Done()
}
func main() {
wg.Add(1)
go worker()
time.Sleep(time.Second * 3) // sleep3
exit = true // goroutine
wg.Wait()
fmt.Println("over")
}
チャンネルモードpackage main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
// :
// 1. , channel
func worker(exitChan chan struct{
}) {
LOOP:
for {
fmt.Println("worker")
time.Sleep(time.Second)
select {
case exitChan: //
break LOOP
default:
}
}
wg.Done()
}
func main() {
var exitChan = make(chan struct{
})
wg.Add(1)
go worker(exitChan)
time.Sleep(time.Second * 3) // sleep3
exitChan struct{
}{
} // goroutine
close(exitChan)
wg.Wait()
fmt.Println("over")
}
公式版の案package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func worker(ctx context.Context) {
LOOP:
for {
fmt.Println("worker")
time.Sleep(time.Second)
select {
case ctx.Done(): //
break LOOP
default:
}
}
wg.Done()
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 3)
cancel() // goroutine
wg.Wait()
fmt.Println("over")
}
子goroutineがまた他のgoroutineを開く時、ctxを入れてもいいです。package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func worker(ctx context.Context) {
go worker2(ctx)
LOOP:
for {
fmt.Println("worker")
time.Sleep(time.Second)
select {
case ctx.Done(): //
break LOOP
default:
}
}
wg.Done()
}
func worker2(ctx context.Context) {
LOOP:
for {
fmt.Println("worker2")
time.Sleep(time.Second)
select {
case ctx.Done(): //
break LOOP
default:
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 3)
cancel() // goroutine
wg.Wait()
fmt.Println("over")
}
Cotext初見Go 1.7は、単一の要求を処理するための複数のgoroutineと要求ドメインのデータ、キャンセル信号、締切時間などの関連動作を単純化するための
context
タイプを定義する新しい標準ライブラリContext
を追加している。これらの動作は複数のAPI呼び出しに関連する可能性がある。サーバからの要求はコンテキストを作成し、サーバへの呼び出しはコンテキストを受け入れるべきです。これらの間の関数コールチェーンは、コンテキストを伝達しなければならない、または
WithCancel
、WithDeadline
、WithTimeout
、またはWithValue
によって作成された派生コンテキストを使用してもよい。コンテキストがキャンセルされると、その派生したすべてのコンテキストもキャンセルされます。コネクタ
context.Context
は、4つの実装が必要な方法を定義するインターフェースである。具体的な署名は以下の通りです。type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() chan struct{
}
Err() error
Value(key interface{
}) interface{
}
}
その中:Deadline
方法は、現在のContext
がキャンセルされた時間、すなわち、仕事の終了時間を返す必要がある。Done
方法はChannel
に戻る必要があります。このChanelは現在の作業が完了した後、またはコンテキストがキャンセルされた後にオフになります。何度も呼び出します。Done
方法は同じChanelに戻ります。ErrErr Context , Done Channel ;
Context
がキャンセルされたらCanceled
エラーに戻ります。Context
がタイムアウトしたらDeadlineExceeded
エラーに戻ります。Value
方法は、Context
からキーに対応する値を返します。同じ文脈では、Value
を何度も呼び出して同じKey
に入ってきたら、同じ結果を返します。Goは、2つの関数を内蔵する。
Background()
とTODO()
と、それぞれContext
インターフェースを実装したbackground
とtodo
とを返す。私たちのコードの最初は、この2つの内蔵されたコンテキストオブジェクトを最上位のpartent context
として、より多くのサブコンテキストオブジェクトを生み出しています。Background()
は主にmain関数、初期化およびテストコードの中でContectというツリー構造の最上位のContectであり、つまり根Contectである。TODO()
、まだ具体的な使用シーンが分かりません。もし私達が何を使うべきか分からないなら、これを使ってもいいです。background
およびtodo
は、本質的にはemptyCtx
構造体タイプであり、キャンセル不可能であり、締切時間が設定されておらず、いかなる値も持たないContectである。Withシリーズ関数
さらに、
context
パケットには4つのWithシリーズ関数が定義されている。WithCacel
WithCancel
の関数署名は以下の通りである。func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithCancel
は、新しいDoneチャネルを有する親ノードのコピーを返す。戻ってきたcancel関数を呼び出したり、親コンテキストのDuneチャネルをオフにすると、コンテキストに戻るDuneチャネルが閉じられます。このコンテキストをキャンセルすると、関連するリソースが解放されますので、コードはこのコンテキストで実行されるべき操作が完了したら、すぐにキャンセルを呼び出すべきです。
func gen(ctx context.Context) chan int {
dst := make(chan int)
n := 1
go func() {
for {
select {
case ctx.Done():
return // return goroutine,
case dst n:
n++
}
}
}()
return dst
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // cancel
for n := range gen(ctx) {
fmt.Println(n)
if n == 5 {
break
}
}
}
上記の例示的なコードでは、gen
関数は、個々のgoroutineにおいて整数を生成し、それらを返すチャネルに送信する。genの使用者は、生成された整数を使用した後、コンテキストをキャンセルする必要があり、gen
が起動している内部goroutineが漏洩しないようにする。WithDeadline
WithDeadline
の関数署名は以下の通りである。func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
親コンテキストのコピーを返し、deadlineをdに遅くないように調整します。父の文脈のdeadlineがすでにdより早い場合、WithDeadlineは語義的には父の文脈に等しい。締め切りが切れた時、リターンのcancel関数を呼び出した時、または親コンテキストのDuneチャネルがオフになった時、コンテキストに戻るDuneチャネルは閉じられます。最初に発生した場合に準じます。このコンテキストをキャンセルすると、関連するリソースが解放されますので、コードはこのコンテキストで実行されるべき操作が完了したら、すぐにキャンセルを呼び出すべきです。
func main() {
d := time.Now().Add(50 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
// ctx , cancel 。
// , 。
defer cancel()
select {
case time.After(1 * time.Second):
fmt.Println("overslept")
case ctx.Done():
fmt.Println(ctx.Err())
}
}
上記のコードでは、50ミリ秒後に期限が切れるdeadlineを定義し、context.WithDeadline(context.Background(), d)
を呼び出してコンテキスト(ctx)とキャンセル関数(cancel)を得てから、selectを使ってメインプログラムを待機させます。1秒後にプリントoverslept
が終了するか、またはctxが期限が切れるまで待って終了します。ctx 50秒後に期限が切れますので、ctx.Done()
は先に値を受け取ります。上のコードはctx.Err()を印刷します。WithTimeout
WithTimeout
の関数署名は以下の通りである。func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
WithTimeout
はWithDeadline(parent, time.Now().Add(timeout))
を返します。このコンテキストをキャンセルすると、関連するリソースが解放されますので、コードはこのコンテキストで実行されるべき操作が完了したら、すぐにキャンセルを呼び出します。通常はデータベースまたはネットワーク接続のタイムアウト制御に使用されます。具体例は以下の通りです。
package main
import (
"context"
"fmt"
"sync"
"time"
)
// context.WithTimeout
var wg sync.WaitGroup
func worker(ctx context.Context) {
LOOP:
for {
fmt.Println("db connecting ...")
time.Sleep(time.Millisecond * 10) // 10
select {
case ctx.Done(): // 50
break LOOP
default:
}
}
fmt.Println("worker done!")
wg.Done()
}
func main() {
// 50
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 5)
cancel() // goroutine
wg.Wait()
fmt.Println("over")
}
WithValue
関数は、要求されたスコープのデータをContectオブジェクトと関係付けることができる。以下のように声明しますfunc WithValue(parent Context, key, val interface{
}) Context
WithValue
は、keyに関連する値がvalである親ノードのコピーを返す。APIとプロセス間の要求領域を伝達するデータのみに、コンテキスト値を使用して、オプションのパラメータを関数に伝達するために使用されるのではない。
提供されたキーは比較可能であり、コンテキストを使用してパケット間で衝突が起こることを避けるために、
string
タイプまたは他の任意の内蔵タイプであるべきではない。WithValue
のユーザは、キーのために自分のタイプを定義しなければならない。interface{}に割り当てられたときに割り当てられることを避けるために、コンテキストキーは通常、特定のタイプstruct{}
を有する。あるいは、導出されたコンテキストキー変数の静的なタイプは、ポインタまたはインターフェースであるべきである。package main
import (
"context"
"fmt"
"sync"
"time"
)
// context.WithValue
type TraceCode string
var wg sync.WaitGroup
func worker(ctx context.Context) {
key := TraceCode("TRACE_CODE")
traceCode, ok := ctx.Value(key).(string) // goroutine trace code
if !ok {
fmt.Println("invalid trace code")
}
LOOP:
for {
fmt.Printf("worker, trace code:%s
", traceCode)
time.Sleep(time.Millisecond * 10) // 10
select {
case ctx.Done(): // 50
break LOOP
default:
}
}
fmt.Println("worker done!")
wg.Done()
}
func main() {
// 50
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)
// trace code goroutine
ctx = context.WithValue(ctx, TraceCode("TRACE_CODE"), "12512312234")
wg.Add(1)
go worker(ctx)
time.Sleep(time.Second * 5)
cancel() // goroutine
wg.Wait()
fmt.Println("over")
}
Contextを使用する注意事項クライアントタイムアウトキャンセル例
// context_timeout/server/main.go
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
", 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
", 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
", result.err)
return
}
defer result.resp.Body.Close()
data, _ := ioutil.ReadAll(result.resp.Body)
fmt.Printf("resp:%v
", string(data))
}
}
func main() {
// 100
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100)
defer cancel() // cancel goroutine
doCall(ctx)
}