Go36-32-context.Context
context.Context
sync.WaitGroupタイプは、1対のマルチgoroutineコラボレーションプロセスを実現する同期ツールです.このコラボレーションプロセスを実現する別のツールもあります.
振り返るWaitGroupによるコラボレーションプロセスの実装
WaitGroupを使用する場合は、「Addを統合してからDoneを同時実行し、最後にWait」というモードでコラボレーションプロセスを構築することをお勧めします.Addメソッドの同時呼び出しを回避します.これは、サブタスクを実行するgoroutineの数を最初から決定する必要があり、少なくともgoroutineを起動する前に問題をもたらす.次の例では、いくつかの改造を行います.package main
import (
"time"
"fmt"
"sync"
"sync/atomic"
)
func coordinateWithWaitGroup() {
total := 12
var num int32
var wg sync.WaitGroup
// goroutine defer
deferFunc := func() {
wg.Done()
}
for i := 0; i < total; i++ {
wg.Add(1)
go addNum(&num, i, deferFunc)
}
wg.Wait()
}
// defer
func addNum(numP *int32, id int, deferFunc func()) {
defer deferFunc()
for i := 1; ; i++ {
currNum := atomic.LoadInt32(numP)
newNum := currNum + 1
time.Sleep(time.Millisecond * 200)
if atomic.CompareAndSwapInt32(numP, currNum, newNum) {
fmt.Printf("id: %02d %02d num : %d
", id, i, newNum)
break
}
}
}
func main() {
coordinateWithWaitGroup()
}
ここの改造は、後でcontextパッケージを使うときの使い方に似ていますが、使用ルールではWaitGroupの要件を満たしています.
contextパッケージによるコラボレーションプロセス
ここでは、上記のcoordinateWithWaitContext関数の代わりにcoordinateWithWaitGroup関数を書きます.2つの関数は同じ機能を持つ必要があります.ここでは、サンプルコードを直接示します.func coordinateWithWaitContext() {
total := 12
var num int32
cxt, cancelFunc := context.WithCancel(context.Background())
// goroutine defer , cancelFunc
deferFunc := func() {
if atomic.LoadInt32(&num) == int32(total) {
cancelFunc()
}
}
for i := 0; i < total; i++ {
go addNum(&num, i, deferFunc)
}
すべての変化は上の関数にあります.ここではcontextが相次いで呼び出された.Background関数とcontext.WithCancel関数.取り消し可能なcontextが得られた.変数cxtに割り当てられたContextタイプの値.もう一つCancelFuncタイプの取り消し関数は、変数cancelFuncに割り当てられます.ここでgoroutineの実行完了を判断する根拠はnumの値を判断することである.完了と判断すると、前に用意したcancelFunc関数が呼び出され、cxt.Done関数が返すチャネルは値を受け取り,待機を終了する.
WaitGroupとの比較WaitGroupは、すべてのgoroutineの数を事前に知る必要がありますが、contextでは、ある条件を満たすかどうかがより関心を持っており、条件が満たされれば終了できます.ここでpythonについてお話ししたいのですが、pythonのforループとwhileループを思い浮かべました.forサイクルが使えるならwhileサイクルは使わないでください.whileサイクルを使用すると、条件判断が複雑になり、条件が永遠に満たされないため、デッドサイクルになる可能性があります.forループを使うとこの問題はありません.しかし、ループの終了が数と関係ない場合はwhileループしか使用できません.WaitGroupのようなもので、goroutineの数で判断できるならWaitGroupを使ったほうがいいと思います.終了条件がgoroutine数に関係ない場合はcontextしか使えません.
context.Contextタイプ
context.Contextタイプは、Go 1.7のリリース時に標準ライブラリに追加されます.その後、標準ライブラリの他の多くのコードパケットは、os/execパケット、netパケット、database/sqlパケット、runtime/pprofパケット、runtime/traceパケットなど、それをサポートするために拡張されています.多くのコードパッケージが積極的にサポートされているのは、非常に一般的な同期ツールであるためです.その値は任意に拡散できるだけでなく、追加の情報と信号を伝達するためにも使用することができる.つまりContextタイプは、コンテキストを表す値を提供することができ、これらの値は同時的に安全であり、すなわち複数のgoroutineに伝播することができる.
インタフェースタイプContextの最新は実際にはインタフェースタイプであり、contextパッケージでそのインタフェースを実現するすべてのプライベートタイプは、あるデータ型に基づくポインタタイプである.したがって、このような伝播は、このタイプの値の機能とセキュリティに影響を与えません.
派生可能なContextタイプの値は、派生可能であり、これは、1つのContext値によって任意のサブ値を生成できることを意味する.これらのサブ値は、親値の属性およびデータを運ぶことも、親値を介して伝達される信号に対応することもできる.このように,すべてのContext値は,コンテキストの全貌を表す属性構造を共に構成する.ツリーのルートノードは、contextパケットで事前に定義されているcontext値であり、グローバルで一意です.contextを呼び出す.Background関数は、それを取得できます.
パッケージ内の関数はcontextパッケージにあり、Context値を派生させるための4つの関数が含まれています.
package main
import (
"time"
"fmt"
"sync"
"sync/atomic"
)
func coordinateWithWaitGroup() {
total := 12
var num int32
var wg sync.WaitGroup
// goroutine defer
deferFunc := func() {
wg.Done()
}
for i := 0; i < total; i++ {
wg.Add(1)
go addNum(&num, i, deferFunc)
}
wg.Wait()
}
// defer
func addNum(numP *int32, id int, deferFunc func()) {
defer deferFunc()
for i := 1; ; i++ {
currNum := atomic.LoadInt32(numP)
newNum := currNum + 1
time.Sleep(time.Millisecond * 200)
if atomic.CompareAndSwapInt32(numP, currNum, newNum) {
fmt.Printf("id: %02d %02d num : %d
", id, i, newNum)
break
}
}
}
func main() {
coordinateWithWaitGroup()
}
func coordinateWithWaitContext() {
total := 12
var num int32
cxt, cancelFunc := context.WithCancel(context.Background())
// goroutine defer , cancelFunc
deferFunc := func() {
if atomic.LoadInt32(&num) == int32(total) {
cancelFunc()
}
}
for i := 0; i < total; i++ {
go addNum(&num, i, deferFunc)
}
これらの関数の最初のパラメータタイプはcontextです.Context、名前はparentです.名前の通り、この位置のパラメータはContext値を生成する親値に対応しています.
コンテキストツリーでの信号の伝播の取り消し
contextパッケージのWithCancel、WithDeadline、およびWithTimeoutは、所与のCOntext値に基づいて取り消し可能なサブ値を生成するために使用される.
WithCancelという関数は呼び出された後、2つの結果値を生成します.1つ目は取り消し可能なContext値であり、2つ目は取り消し信号をトリガするための関数である.取り消し関数が呼び出されると、対応するContext値はまず内部の受信チャネルを閉じ、チャネルが受信チャネルを閉じる操作はすぐに返されます.Doneメソッドが返すチャネルです.その後、すべてのサブ値に取り消し信号も伝達されます.これらのサブ値にサブ値がある場合、取り消し信号は1段1段伝達されます.最後に、このContext値は親値との関連付けを解除します.
WithDeadlineおよびWithTimeoutは、WithDeadline関数またはWithTimeout関数を呼び出すことによって生成されるContext値も取り消すことができる.これらは、手動で取り消すだけでなく、生成が所定の期限切れであることに基づいて、自動的にタイミングで取り消すことができる.ここでのタイミング取り消し機能は、それらの内部のタイマによって実現される.有効期限が到来すると、2つのContext値の動作は手動で取り消す動作とほぼ一致し、内部のタイマーを停止して解放するだけです.WithDeadlineとWithTimeoutは似ています.いずれも設定により、ある時間に自動的にトリガctxである.Done()は値を取得できます.違いは、DeadLineは時点を設定し、時間が合えば期限が切れることです.Timeoutは、数秒などの時間を設定し、この時間が過ぎるとタイムアウトします.実は下層のTimeoutもDeadlinによって実現されています.
WithValueという関数で得られた値は取り消すことができません.取り消し信号は、伝播時に直接スパンされ、サブ値に直接情報を伝達しようとします.
データの転送
WithValue関数で新しいContext値を生成するには、親、キー、値の3つのパラメータが必要です.ここでキーは、辞書のような判断可能なキーでなければなりません.ただし、Context値は、辞書でキーと値を格納するのではなく、親の値に対応するフィールドに簡単に格納されます.Valueメソッドでは、データを取得できます.属性を含むContext値を呼び出すValueメソッドは、指定されたキーを先に判断し、ある場合は格納された値を返します.そうしないと、親値まで検索を続け、コンテキストルートノードのメソッドに沿って検索されます.他のいくつかのContext値はデータを持ち運ぶことができないため、Valueメソッドは検索時にこれらのContext値を越えます.
データContextインタフェースを変更できないため、データを変更する方法はありません.通常、コンテキスト数にデータを含むContext値を追加することで新しいデータを格納するか、この値の親値を取り消すことで対応するデータを破棄するしかありません.ここに格納されているデータが外部から変更できる場合は、安全を保証する自信が必要です.
次の例では、Context値におけるデータの転送を示します.
package main
import (
"context"
"fmt"
"time"
)
type myKey int
func main() {
keys := []myKey{
myKey(20),
myKey(30),
myKey(60),
myKey(61),
}
values := []string{
"value in node2",
"value in node3",
"value in node6",
"value in node6Branch",
}
rootNode := context.Background()
node1, cancelFunc1 := context.WithCancel(rootNode)
defer cancelFunc1()
node2 := context.WithValue(node1, keys[0], values[0])
node3 := context.WithValue(node2, keys[1], values[1])
fmt.Printf("The value of the key %v found in the node3: %v
",
keys[0], node3.Value(keys[0]))
fmt.Printf("The value of the key %v found in the node3: %v
",
keys[1], node3.Value(keys[1]))
fmt.Printf("The value of the key %v found in the node3: %v
",
keys[2], node3.Value(keys[2]))
fmt.Println()
node4, cancelFunc4 := context.WithCancel(node3)
defer cancelFunc4()
node5, cancelFunc5 := context.WithTimeout(node4, time.Hour)
defer cancelFunc5()
fmt.Printf("The value of the key %v found in the node5: %v
",
keys[0], node5.Value(keys[0]))
fmt.Printf("The value of the key %v found in the node5: %v
",
keys[1], node5.Value(keys[1]))
fmt.Println()
node6 := context.WithValue(node5, keys[2], values[2])
fmt.Printf("The value of the key %v found in the node6: %v
",
keys[0], node6.Value(keys[0]))
fmt.Printf("The value of the key %v found in the node6: %v
",
keys[2], node6.Value(keys[2]))
fmt.Println()
node6Branch := context.WithValue(node5, keys[3], values[3])
fmt.Printf("The value of the key %v found in the node6Branch: %v
",
keys[1], node6Branch.Value(keys[1]))
fmt.Printf("The value of the key %v found in the node6Branch: %v
",
keys[2], node6Branch.Value(keys[2]))
fmt.Printf("The value of the key %v found in the node6Branch: %v
",
keys[3], node6Branch.Value(keys[3]))
fmt.Println()
node7, cancelFunc7 := context.WithCancel(node6)
defer cancelFunc7()
node8, cancelFunc8 := context.WithTimeout(node7, time.Hour)
defer cancelFunc8()
fmt.Printf("The value of the key %v found in the node8: %v
",
keys[1], node8.Value(keys[1]))
fmt.Printf("The value of the key %v found in the node8: %v
",
keys[2], node8.Value(keys[2]))
fmt.Printf("The value of the key %v found in the node8: %v
",
keys[3], node8.Value(keys[3]))
}
まとめ
Contextタイプは、マルチgoroutineコラボレーションプロセスの同期を実現するツールです.また、その値によって取り消し信号やデータの転送を伝達することもできる.Contextタイプの値は大きく3つに分けられます.
このシリーズは理論に偏っていて、多くの実際の応用が少なくなりました.contextパッケージについて、私は前にもう1編ありました.https://blog.51cto.com/steed/2330218この記事では、2つの主な機能について説明します.