Go言語12


context使用説明
主な機能:
  • 制御タイムアウト時間
  • コンテキストデータ
  • を保存する.
    context処理によるタイムアウト
    きほんぶんぽうこうぞう
    import ""context""
    
    //         
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    
    //     
    select {
    case 

    例-Webサイトへのアクセスタイムアウト
    ここでは最下位のrequestでGETリクエストを送信します.&http.Client{}の構造体自体にもTimeoutの設定があり、デフォルトの0値はタイムアウトを設定しないことです.さらにClinetはそのTransportがCancelRequestメソッドを実装しなければならないことを要求し、デフォルトのTransportにはこのメソッドがある.次の例は、最下位の論理をシミュレーションし、タイムアウト後にTransportを手動で呼び出すCancelRequestメソッドです.
    package main
    
    import (
        "context"
        "fmt"
        "os"
        "io/ioutil"
        "net/http"
        "time"
    )
    
    type Result struct {
        r *http.Response
        err error
    }
    
    func process(host string) {
        ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
        defer cancel()  // cancel     ,             
    
        c := make(chan Result)
        tr := &http.Transport{}
        client := &http.Client{Transport: tr}
        req, err := http.NewRequest("GET", "http://" + host, nil)
        if err != nil {
            fmt.Fprintf(os.Stderr, "HTTP GET ERROR: %v
    ", err) return } go func() { resp, err := client.Do(req) c

    コマンド・ラインは、要求されたサーバ・アドレスとして最初のパラメータを受信し、結果を実行します.
    PS H:\Go\src\go_dev\day12\context> go run main.go google.com
    Timeout... Get http://google.com: net/http: request canceled while waiting for connection
    PS H:\Go\src\go_dev\day12\context> go run main.go baidu.com
    Server Response:
    
    
    
    
    PS H:\Go\src\go_dev\day12\context>

    1回のリクエストがタイムアウトし、1回に結果が返されます.
    コンテキストをcontextで保存する
    contextを使用すると、いくつかのカスタムパラメータ伝達もできます.key-valueの形式でcontextの変数に格納し、使うときに取り出します.次の例ではcontextを試して変数を渡します.
    package main
    
    import (
        "fmt"
        "context"
    )
    
    func process(ctx context.Context) {
        age, ok := ctx.Value("age").(int)  //                        
        if !ok {
            age = 18  //      ,      18
        }
        fmt.Println("Age:", age)
        name, _ := ctx.Value("name").(string)  //     
        fmt.Println("Name:", name)
        fmt.Println(ctx.Value("gender"))  // ctx           ,               
        fmt.Println(ctx.Value("gender1"))  //        key,   nil
    }
    
    func main() {
        ctx := context.WithValue(context.Background(), "name", "Adam")  //       , 2      key value
        ctx = context.WithValue(ctx, "age", 23)  //    ,      ctx,         
        ctx = context.WithValue(ctx, "gender", "Male")
        process(ctx)  //     , ctx   ,           
    }

    ここではただ使い方を示すだけで、例の中のこのような明確な変数は、伝統的な方法で伝えるべきです.グローバルに必要な変数はcontextを使用してメンテナンスできます.contextを使うと、変数の情報が隠されてしまうので、コードの読みやすさが悪くなります.しかも変数のタイプが隠されていてgoの習慣にも合わないので、上で使う前に、タイプ断言をしていました.
    contextでgoroutineを終了
    context.WithCancelメソッドでは、goroutineのライフサイクルも制御できます.
    //       
    ctx, cancel:= context.WithCancel(context.Background())
    defer cancel()
    
    //   cancel() ,ctx.Done()          
    go func() {
        //          ,  context  ,         
        for {
            select {
            case 

    ここでは2つの変数,ctx,cancelを定義した.cancelは呼び出し可能な関数で、呼び出しが実行された後です.ctx.Doneというパイプで値を取り出すことができます.goroutineではこのパイプでgoroutineの脱退を制御できます.完全な例は次のとおりです.
    package main
    
    import (
        "time"
        "fmt"
        "context"
    )
    
    func test() {
        //            ,     
        gen := func(ctx context.Context) 

    上記の例の適用シーンは、goroutineを有効にしてタスクを実行し、このgoroutineが終了したことを通知する必要がある場合、ここのcontextで実現できます.
    DeadLineタイムアウト
    WithDeadlineとWithTimeoutは似ています.いずれも設定により、ある時間に自動的にトリガーされます.ctx.Done()は値を取得できます.違いは、DeadLineは時点を設定し、時間が合えば期限が切れることです.Timeoutは、数秒などの時間を設定し、この時間が過ぎるとタイムアウトします.実は下層のTimeoutもDeadlinによって実現されており、Timeoutでは直接return WithDeadline(parent,time.Now().Add(timeout)).次の例では、50ミリ秒のタイムアウトを設定し、WithDeadlineで設定します.実行プログラムはコマンドラインパラメータを受信し、整数を入力します.このミリ秒の時間が経過すると、カスタムコンテンツが出力されます.数値が大きい(50より大きい)とタイムアウトし、contextのErrメソッドが返す情報が出力されます.
    package main
    
    import (
        "context"
        "fmt"
        "time"
        "os"
        "strconv"
    )
    
    func main() {
        n, _ := strconv.Atoi(os.Args[1])  //           ,    
        d := time.Now().Add(50 * time.Millisecond)  // 50     
        ctx, cancel := context.WithDeadline(context.Background(), d)  //    Timeout,       Add    
        defer cancel()
    
        select {
        case  go run main.go 123
    context deadline exceeded
    PS H:\Go\src\go_dev\day12\context\deadline> go run main.go 12
        
    PS H:\Go\src\go_dev\day12\context\deadline>
    */

    この例はTimeoutを使うほうが便利ですが、ここでは主にDeadlineの使い方を示します.2つの方法の効果は同様で,実際の要求に基づいて適切な方法を選択する.TImeoutはより使いやすいはずなので、Deadineをさらにカプセル化し、より多くのアプリケーションシーンで使用するためのTimeoutメソッドを提供します.
    sync.WaitGroupの紹介
    これまではパイプを介してgoroutineとデータを伝達していたが,パイプを介して待機を実現することもできた.しかし、goroutineの実行が完了するのを待つだけでは、まだ実現できる方法があります.sync.WaitGroupを使用することで、goroutineのセットが終わるのを簡単に待つことができます.具体的には、次の3つのステップです.
  • Addメソッドを使用して待機数を設定し、カウントに1
  • を加算する.
  • Doneメソッドを使用して待機数を設定し、カウントを1
  • 減らします.
  • 待機数が0に等しい場合、Waitメソッドは
  • を返す.
    例1
    次はhttpリクエストの例です.すべてのリクエストが戻ってくるまでメイン関数を終了しません.
    package main
    
    import (
        "os"
        "sync"
        "fmt"
        "net/http"
    )
    
    var wg sync.WaitGroup
    
    func main() {
        var urls = []string {
            "baidu.com",
            "51cto.com",
            "go-zh.org",
        }
        for _, url := range urls {
            wg.Add(1)  //     goroutine,   1
            go func(url string) {
                defer wg.Done()  //       1
                resp, err := http.Head("http://" + url)
                if err != nil {
                    fmt.Fprintf(os.Stderr, "%s Head ERROR: %v", url, err)
                    return
                }
                fmt.Println(*resp)
            }(url)
        }
        wg.Wait()  //      ,      ,   
        fmt.Println("All Requests Down")
    }

    例2
    次の例では、別のスタイルの書き方を示します.
    package main
    
    import (
        "fmt"
        "sync"
        "time"
    )
    
    func calc(w *sync.WaitGroup ,i int) {
        defer w.Done()
        fmt.Println("calc:", i)
        time.Sleep(time.Second)
    }
    
    func main() {
        //      wg   ,           
        //          ,      
        wg := sync.WaitGroup{}
        wg.Add(10)  //    10,  for      1 
        for i := 0; i < 10; i++ {
            go calc(&wg, i)  //        ,      
        }
        wg.Wait()
        fmt.Println("All goroutine Done")
    }

    小結
    注意:Addメソッドはgoroutineに入れてはいけません.問題ないように見えますが、goroutineが実行されるのを待っていない可能性があります.メイン関数はWaitに実行されます.カウントがまだ始まっていない0の効果で、主関数は実行され続けます.メイン関数が先にWaitを実行するかgoroutineが先にAddを実行するかは運次第でパイプより使いやすく理解しやすく、goroutineのセットを待つことができます.この方法は,goroutine実行の終了を待つ主関数しか実現できない.goroutineの終了を通知する必要がある場合は、パイプで実現します.パイプはインタラクティブなデータに使用できるので、すべての状況が適用されます.sync.WaitGroupシーンは単一ですが、より使いやすいです.