でgolang制御の同時実行(sync.WaitGroupとcontext.Context) 、我々はコンテキストの役割とその簡単な使用について話してきた。今度はそれがコンテキストに関する他の事について話を広げましょう
コンテキストインターフェイス
Contextのインターフェース定義は比較的簡潔です。このインターフェースのメソッドを見てみましょう。
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{
}
Err() error
Value(key interface{
}) interface{
}
}
このインターフェースには4つのメソッドがあります。これらのメソッドをより適切に使用するには、これらのメソッドの意味を理解することが非常に重要です。
Deadlineメソッドは、設定された期限を取得することです。最初の戻り値の型は期限です。この時点で、Contextは自動的にキャンセル要求を開始します。2番目の戻り値ok == falseは、必要に応じて期限が設定されていないことを意味します。キャンセルするには、キャンセル機能を呼び出してキャンセルする必要があります。
Doneメソッドは、タイプがstruct {}の読み取り専用chanを返します。現在、問題が発生しています。このメソッドによって返されたchanを読み取ることができる場合は、親コンテキストがキャンセル要求を開始したことを意味します。このシグナルを受信した後、 Doneメソッド、クリーンアップ操作を実行してから、ゴルーチンを終了してリソースを解放する必要があります。
Errメソッドは、キャンセルされたコンテキストが原因で、キャンセルのエラー理由を返します。
Valueメソッドは、キーと値のペアであるContextにバインドされた値を取得するため、対応する値はKeyを介して取得できます。この値は通常、スレッドセーフです。
上記の4つの方法の中で最も一般的に使用されるのはDoneです。コンテキストがキャンセルされると、閉じたchanを取得できます。閉じたchanは、読み取れる限り、コンテキストがキャンセルされたことを意味します。以下は、このメソッドの典型的な使用法です。
func Stream(ctx context.Context, out chan<- Value) error {
for {
v, err := DoSomething(ctx)
if err != nil {
return err
}
select {
case <-ctx.Done():
return ctx.Err()
case out <- v:
}
}
}
Contextインターフェースを実装する必要はありません。Goの組み込みは2つを実装するのに役立ちました。コードの最初に、これら2つの組み込みを最上位のpartentコンテキストとして使用して、より多くの子コンテキストを派生させました。
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
1つはBackground
、主にメイン関数、初期化、およびテストコードで、ルートコンテキストであるコンテキストツリー構造の最上位コンテキストとして使用されます。
1つはTODO
、現在、特定の使用シナリオがわからないことです。使用するコンテキストがわからない場合は、これを使用できます。
それらは両方とも本質的にemptyCtx構造タイプであり、期限が設定されておらず、値もないキャンセル不可能なコンテキストです。
type emptyCtx int
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}
func (*emptyCtx) Done() <-chan struct{
} {
return nil
}
func (*emptyCtx) Err() error {
return nil
}
func (*emptyCtx) Value(key interface{
}) interface{
} {
return nil
}
これがemptyCtxがContextインターフェースを実装する方法です。ご覧のとおり、これらのメソッドは何もせず、nilまたは0を返します。
コンテキストの継承と派生
上記のルートコンテキストを使用して、より多くの子コンテキストを派生させる方法は?これは、コンテキストパッケージによって提供されるWithシリーズの関数に依存します。
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, val interface{
}) Context
这四个 With 函数,接收的都有一个 partent 参数,就是父 Context,我们要基于这个父 Context 创建出子 Context 的意思,这种方式可以理解为子 Context 对父 Context 的继承,也可以理解为基于父 Context 的衍生。
通过这些函数,就创建了一颗 Context 树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个。
キャンセル関数を使用すると、親コンテキストをパラメーターとして渡し、子コンテキストを返し、キャンセル関数を使用してコンテキストをキャンセルします。WithDeadline関数はWithCancelに似ています。追加の期限パラメーターを渡します。つまり、コンテキストはこの時点で自動的にキャンセルされます。もちろん、キャンセル機能を使用して、この時間を待たずに事前にキャンセルすることもできます。 。
WithTimeoutとWithDeadlineは基本的に同じです。これは、タイムアウト後にコンテキストが自動的にキャンセルされることを意味します。
WithValue関数は、Contextのキャンセルとは関係ありません。これは、キーと値のペアデータでバインドされたContextを生成することです。バインドされたデータには、後で説明するContext.Valueメソッドを介してアクセスできます。
最初の3つの関数はすべて、関数型であり、その定義が非常に単純なキャンセル関数CancelFuncを返すことに気付いたかもしれません。
type CancelFunc func()
これはキャンセル機能のタイプであり、レベルがいくつあっても、このノードコンテキストの下のコンテキストとすべてのコンテキストをキャンセルできます。
WithValue
は、コンテキストを介してメタデータを渡すことで、使用するためにコンテキストに添付される必要なメタデータを渡すこともできます。
var key string = "name"
func main() {
ctx, cancel := context.WithCancel(context.Background())
//附加值
valueCtx := context.WithValue(ctx, key, "【监控1】")
go watch(valueCtx)
time.Sleep(10 * time.Second)
fmt.Println("可以了,通知监控停止")
cancel()
//为了检测监控过是否停止,如果没有监控输出,就表示停止了
time.Sleep(5 * time.Second)
}
func watch(ctx context.Context) {
for {
select {
case <-ctx.Done():
//取出值
fmt.Println(ctx.Value(key), "监控退出,停止了...")
return
default:
//取出值
fmt.Println(ctx.Value(key), "goroutine监控中...")
time.Sleep(2 * time.Second)
}
}
}
前の例では、パラメータを渡すことにより、nameの値を監視関数に渡しました。この例では、同じ効果を実現しますが、Contextの値を使用します。
context.WithValueメソッドを使用して、KVキーと値のペアのペアをアタッチできます。ここで、キーは同等、つまり比較可能である必要があり、値はスレッドセーフである必要があります。
このようにして、新しいコンテキストを生成しました。この新しいコンテキストには、このキーと値のペアがあります。使用中は、Valueメソッドを介してctx.Value(key)を読み取ることができます。
WithValueを使用して値を渡すことは一般的に必要な値であり、すべての値を渡すわけではないことを忘れないでください。(原因待查
)
コンテキスト使用の原則
構造体にContextを配置せず
、パラメーターとして渡します。Contextをパラメーターとして受け取る関数メソッドは、Contextを最初のパラメーターとして配置し、最初に配置する必要があります。
Contextを関数メソッドに渡すときは、nilを渡さないでください。何を渡すかわからない場合は、contextを使用してください。TODOContext
のValue関連のメソッドは、必要なデータを渡す必要があります。これを使用してすべてのデータを渡さないでください
。Contextはスレッドです。-安全で安心できます複数のゴルーチンを渡すことができます