Golangは閉鎖されたChannelの残した穴を越えます.


余談をする
CPUの核はますます多くなり、個々の核の計算能力は大きく向上していません.主にシリコンウエハの加工技術が頭打ちになります.品質を上げることができないなら、積み上げるしかないです.NodeJsのような単一スレッドまたは少数スレッドの応用は非常に制限されている.シングルスレッドは一つのコアを走るので、シングルコアの性能を十分に発揮できますが、多くの核優勢を発揮するのは難しいです.全体のサーバー利用率は高くないです.
単一の処理タスクを縮小して大量の小さなタスクに分割し、同時に実行することで、サーバのマルチコア性能を十分に発揮することができます.これは二つの問題に関連しています.一つはオペレーティングシステムのスレッド数が限られていて、占有資源が大きく、数が多すぎるとスレッドスイッチング性能が損失します.もう一つはプログラミングデータの同期を同時に行う問題です.最初の問題はgolangに新しい概念「goroutine」を提供して解決します.これはスレッドよりも軽く、同時に実行できる数が多く、リソースがより少ない擬似スレッドです.ここでは詳しく紹介しません.二つ目の問題を解決するにはもっと難しいです.通常は安全にデータを修正するために大量のスレッドロックが必要です.スレッドロックが多くなると、かえってアプリケーションが不安定になります.プログラム猿はいつも居眠りをしています.併発されたデータが安全に修正されないと、この大きな地雷は埋められます.golangはここで私たちのために「Channel」を準備しました.Channelはスレッドロックを代替するためではなく、各スレッドに流れるデータを安全に加工する便利な道を提供してくれます.
Channelを閉じてChannelを訪問してもたらす異常
チャンネルが閉まると、二つの場合に異常が発生します.
  • は、閉じられたチャンネルにデータを送信する
  • .
  • 閉じられているチャンネルを閉じます.
  • ある場合は異常を出さない.
  • は、閉じられたチャンネルにデータを取得し、いつも一つの空き値
  • を得る.
    どのように静かに送信しますか?受信します.Chanel関連コードは以下の通りです.ここで発生した二つの異常を遮断しました.
    func SafeClose(ch chan T) (justClosed bool) {
        defer func() {
            if recover() != nil { 
                justClosed = false
            }
        }()
    
        close(ch)   
        return true  
    }
    
    func SafeSend(ch chan T, value T) (closed bool) {
        defer func() {
            if recover() != nil {
                closed = true
            }
        }() 
        ch return false  
    }
    
    func SafeReceive(ch chan bool){
        i, ok := if ok { 
            println(i) 
        } else { 
            println("channel closed") 
        } 
    }
    ケース
    以上のやり方はさまざまな場面に適応できません.いくつかのケースから最適な実践を説明します.まずChannelの標準的な流れから言います.マルチスレッド開発では、スレッドを生産してパケットを作成し、Channelに入れます.消費スレッドはChannelからパケットを取り出して処理する.生産スレッドが最後のパケットを送信した後、Channelを閉じて生産任務を終了します.消費スレッドが最後のパケットを処理したところ、Channelが閉鎖されていることが分かりました.消費タスクを終了しました.
    1.消費スレッドが発生して、消費を継続できない
    消費スレッドに異常が発生しました.データパッケージの処理を続けられませんでした.この場合、消費スレッドは閉鎖されていないChannelを残して退出する可能性があります.これは生産スレッドのデッドロックを引き起こします.この場合、Channelを閉じることにより、生産スレッド異常が発生し、送信行為を終了することができます.
    func receive(ch chan int) {
        defer func() {
            if err := recover(); err != nil {
                close(ch)
            }
        }()
    
        for x := range ch {
            //     
            time.Sleep(time.Second) 
            fmt.Println(x)
            if x == 3 {
                //        
                panic("    ,    ...")
            } 
        }
        fmt.Println("    ")
    }
    
    func send(ch chan int) {
    
        x := 0
    
        defer func() {
            if err := recover(); err != nil && (err.(runtime.Error)).Error() == "send on closed channel" { 
                //       
                fmt.Print("    >>>")
                fmt.Println(x) 
            }else {
                //    
                close(ch)
            }
        }()
    
        for i := 0; i < 10; i++ {
            //    
            x++
            ch 
    out put:
    1 2 3レガシーデータ>>4
      もう一つの実現方式.新たにChannelを追加して状態通知を行います.この方法はより多くのシーンを適用することができますが、プログラムを複雑にすることもあります.
    
    func receive(ch, quit chan int) {
        for x := range ch {
            //    
            time.Sleep(time.Second)
            fmt.Println(x)
            if x == 3 {
                //    
                close(quit)
                return
            }
        }
        fmt.Println("    ")
    }
    
    func send(ch, quit chan int) {
        defer func() {
            //    
            close(ch)
        }()
    
        x := 0
        for i := 0; i < 10; i++ {
            //    
            x++
            select {
            case ch case "      ")
                return
            }
        }
    }
    out put:
    1 2 3生産の早期終了
     
    2.消費スレッドが自動的にデータパッケージを終了し、生産を継続する
    上記の例では、最後に生成されたパケットが処理されていない場合があります.消費スレッドは、パケットを自発的に終了して生産を続けていますが、すでに生産されたパケットは、残されないように処理されなければなりません.私たちは上の例をちょっと変えたいだけです.
    func receive(ch, quit chan int) {
        defer func() {
            if err := recover(); err != nil {
                //  
                close(quit)
            }
    
        }()
    
        r := rand.New(rand.NewSource(time.Now().UnixNano()))
        for x := range ch {
            //    
            time.Sleep(time.Second)
            fmt.Println(x)
    
            if x == 5 {
                //      ,        Channel  
                quit  0
            } else if x == r.Intn(10) {
                //      ,        
                panic("error")
            }
    
        }
        fmt.Println("    ")
    }
    
    func send(ch, quit chan int) {
        defer func() {
            //    
            close(ch)
        }()
    
        x := 0
        for i := 0; i < 10; i++ {
            //    
            x++
            select {
            case ch "%d>>", x)
            case _, ok := if ok {
                    fmt.Println("    ")
                    //       
                    ch "%v--", x)
                    return
                } else {
                    fmt.Println("    ")
                    return
                }
            }
        }
    }
    
    out put:
    1>>1>>>2>>3>>3>>4>>5より前に終了します.6–6タスク終了です.
    または
    1>>1異常終了
     
    3.設計上Channelのスレッドを明確に閉じるべきである.
    正常にChannelをオフにしました.もちろん、新しいパケットは送らないということは明らかになりました.そのため、プログラム設計上でChannelが何度も閉鎖されることを避けるべきです.送信および受信が1対Nであるにもかかわらず、N対1またはN対Nである.送信完了フラグを取得するスレッドがあります.同様に、送信完了フラグを最初に受信したスレッドのみが、Channelをクローズ操作する必要があります.逆に、プログラム設計からChannelを複数回閉鎖することはできません.つまり、各スレッドがChannelを閉じるタイミングが混乱している可能性があり、データが送信されていない場合もあります.