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つの関数が含まれています.
  • WithCancelは、取り消し可能なparentのサブ値
  • を生成する.
  • WithDeadlineは、定期的に取り消されるparentのサブ値
  • を生成する.
  • WithTimeoutは、同じくタイミング取り消しのparentのサブ値
  • である.
  • WithValueは、追加のデータを運ぶparentのサブ値
  • を生成する.
    これらの関数の最初のパラメータタイプは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値
  • 取り消し可能なContext値
  • データを含むContext値
  • すべてのContext値は、コンテキストツリーを構成します.このツリーの役割ドメインはグローバルであり、ルートContext値はツリーのルートであり、グローバルで唯一であり、追加の機能は提供されません.データを含むContext値は取り消すことができず、取り消すことができるContext値はデータを運ぶことができない.しかし、それらは共に有機的な全体、すなわちコンテキスト数を構成するため、syncよりも機能的に優れている.WaitGroupはずっと強いです.
    このシリーズは理論に偏っていて、多くの実際の応用が少なくなりました.contextパッケージについて、私は前にもう1編ありました.https://blog.51cto.com/steed/2330218この記事では、2つの主な機能について説明します.
  • 制御タイムアウト時間
  • コンテキストデータ
  • を保存する.