Golang同時Groutineインスタンス解読(二)

19179 ワード

goはsyncパケットとchannelメカニズムを提供し,コヒーレント間の同期と通信を解決する.
一、sync.WaitGroup
syncパッケージのWaitGroupは、キューにタスクを追加することができます.タスクが完了すると、タスクをキューから削除します.キューのタスクがすべて完了していない場合、キューはブロックをトリガーしてプログラムの実行を阻止します.使用方法は、次のコードを参照してください.
package main
import (
    "fmt"
    "sync"
)
var waitgroup sync.WaitGroup
func Afunction(shownum int) {
    fmt.Println(shownum)
    waitgroup.Done() // , -1, .Done .Add(-1)
}
 
func main() {
    for i := 0; i < 10; i++ {
        waitgroup.Add(1) // goroutine, +1
        go Afunction(i)
    }
    waitgroup.Wait() //.Wait() , 
}

syncを利用できますWaitGroupはこのような状況を満たします.
▲ある場所で複数のgoroutineを作成し、必ず実行が完了してから次の操作を続行します.
はい、WaitGroupの最大の利点はWait()は、キューにブロックされたタスクが完了してからブロックを解除できます.
二、channel
channelはgolang内蔵のタイプで、英語の直訳は「チャネル」で、実は、それは本当にパイプで、先進的なデータ構造です.
channelの操作は4つしかありません.
(1)chennelの作成(make()関数による)
(2)データを入れる(channel経由
(3)データの取り出し(通過)
(4)チャネルを閉じる(close()関数を通る)
しかしchannelには非常に強力な性質があります.覚えて理解してください.
(1)channelはパイプをブロックし,自動的にブロックする.つまり、パイプがいっぱいになると、あるroutineがchannelからデータを取り出すまで、channelにデータを入れる操作がブロックされ、このデータを入れる操作が実行されます.逆に同じように、パイプが空の場合、あるroutineがこのchannelにデータを入れるまで、channelからデータを取り出す操作がブロックされ、このデータを取り出す操作が実行されます.これはchannelの最も重要な性質であり、一つもない.
package main
func main() {
    ch := make(chan int, 3)
    ch 1
    ch 1
    ch 1
    ch 1 // , channel 
package main
func main() {
    ch := make(chan int, 3)
    // , channel , , 
}

(2)チャネルはバッファのあるチャネルとバッファのないチャネルに分けられる.2つのchannelの作成方法は次のとおりです.
ch := make(chan int) // channel, make(chan int, 0)
ch := make(chan int, 5) // 5 channel

チャネルを操作するときは、チャネルのブロックがデッドロックを引き起こす操作があるため、バッファがあるかどうかに注意してください.以下、これらの注意すべき光景を説明します.
まず1つの例を見てみましょう.この例は、2つのセグメントが主関数だけで異なるコードです.
package main
 
import "fmt"
 
func Afuntion(ch chan int) {
    fmt.Println("finish")
    ch
}
 
func main() {
    ch := make(chan int) // channel
    go Afuntion(ch)
    ch 1
    
    //// finish
}
package main
 
import "fmt"
 
func Afuntion(ch chan int) {
    fmt.Println("finish")
    ch
}
 
func main() {
    ch := make(chan int) // channel
    // 
    ch 1
    go Afuntion(ch)
 
    ////
}

前のコードは最終的に「finish」を出力し、正常に終了しますが、後のコードでデッドロックが発生します.なぜこのような現象が発生したのか、上の2つのコードの論理を走ってみましょう.
 
最初のセグメントコード:
        1. バッファなしchannelを作成しました
        2. goroutineが起動しました.このroutineではchannelに対して取り出し操作を実行していますが、このときchannelが空なので、この取り出し操作はブロックされていますが、メインroutineではブロックされていません.まだ実行中です.
        3. メインgoroutineはこのとき次の行を実行し続け、channelにデータを入れます.
        4. このときブロックしていたそのroutineがchannelにデータがあることを検出したので,接触ブロックしてchannelからデータを取り出し,プログラムはこれで終了する.
 
2番目のセグメントコード:
        1. バッファレスchannelを作成しました
        2. プライマリroutineはchannelにデータを入れますが、channelにはバッファがなく、channelが常にいっぱいであることに相当するため、ここでブロックが発生します.しかし、次のgoroutineはまだ作成されていません.主routineがここでブロックされると、プログラム全体がこのようにブロックされ続けるしかありません.そして..そしてその後はありません.のデッドロック!
 
※ここからわかるように、バッファのないチャンネルでは、入出操作と取出操作が同一のroutineではなく、あるroutineが取出操作を実行していることを確認してから、別のroutineで入操作を実行する必要があります.
 
バッファ付きchannelについては、それほどこだわりはありませんが、バッファがあるので、バッファが不満であれば、入れ操作でブロックされません.同様に、バッファが空でなければ、取り出し操作でブロックされません.また,バッファ付きchannelの出し入れと取り出しは同じroutineで使用できる.
しかし、バッファがあれば勝手にチャンネルの入れや取り出しができるというわけではありませんので、入れや取り出しの速度の問題には注意しなければなりません.この問題を例に挙げて説明します.
現在実行されているgoroutineの合計数を、channelの自動ブロックの性質を利用して制御することがよくあります.以下のようにします.
package main
 
import (
    "fmt"
)
 
func Afunction(ch chan int) {
    fmt.Println("finish")
    //goroutine channel 
}
 
func main() {
    ch := make(chan int, 10)
    for i := 0; i < 1000; i++ {
        // goroutine channel , 10 , 
        // , goroutine <=10 
        ch 1
        go Afunction(ch)
    }
    //  , , 
}

上記のchannelの使用方法はほとんどよく使われていますが、次のコードを見てみると、上記のchannelの使用方法とほぼ同じですが、問題が発生します.
package main
func Afunction(ch chan int) {
    ch 1
    ch 1
    ch 1
    ch 1
    ch 1
 
    ch
}
 
func main() {
    // routine 
    ch := make(chan int, 10)
    for i := 0; i < 100; i++ {
        ch 1
        go Afunction(ch)
    }
 
    //  
}

上記の運転は前の運転と基本的に原理は同じですが、運転後にデッドロックが発生します.どうしてですか.まとめてみると、「早すぎる、遅すぎる」という言葉があります.
本来、私たちはメインroutineでサブgoroutineを作成し、channelにデータを入れるたびに、サブgoroutineはchannelからデータを取り出す責任を負います.しかし、私たちのこのコードはサブgoroutineを作成した後、各routineはchannelに5つのデータを入れます.これにより、チャネルに6つのデータを入れるたびに取り出し操作が実行されるため、ある時点でチャネルがいっぱいになっている可能性がありますが、すべてのroutineが挿入操作を実行している(現在、挿入操作を実行する確率が取り出し操作を実行する6倍であるため)ため、すべてのroutineがブロックされ、デッドロックが発生します.
バッファ付きチャンネルを使用する場合は、入れる速度と取り出す速度の問題に注意してください.
(3)閉じたチャネルはデータを取ることができるが,データを置くことはできない.また、channelはclose()を実行した後、本当に閉じられず、channelのデータがすべて取り出されてから本当に閉じられます.
package main
func main() {
    ch := make(chan int, 5)
    ch 1
    ch 1
    close(ch)
    ch 1 // channel 
        
        //  panic
}
package main
func main() {
    ch := make(chan int, 5)
    ch 1
    ch 1
    close(ch)
    // channel , 
 
        // 
}
package main
 
import "fmt"
 
func main() {
    ch := make(chan int, 5)
    ch 1
    ch 1
    ch 1
    ch 1
    close(ch)  // close() channel , 
    for {
        data, ok := ch
        if !ok {
            break
        }
        fmt.Println(data)
    }
    
    //// 1
    // 1
    // 1
    // 1
    // 
    //  close() , channel ,channel 
}


 
三、チャンネルを使ってgoroutine数量を制御する
チャンネルの性質はここまでで紹介しましたが、チャンネルの使用はWaitGroupよりも詳細に注意しているように見えますが、チャンネルで同期を実現しなければならない理由は何ですか?channelはWaitGroupに比べて大きな利点があります.channelは、コヒーレントな同期を実現するだけでなく、現在実行中のgoroutineの総数を制御することができます.
channelを使用してgoroutineの数を制御する方法をいくつか紹介します.
1.タスク数が一定の場合:
ackage main
func Afunction(ch chan int) {
    ch 1
}
 
func main() {
    var (
        ch        chan int = make(chan int, 20) // routine 20
        dutycount int      = 500
    )
    for i := 0; i < dutycount; i++ {
        go Afunction(ch)
    }
 
    // , routine 
    for i := 0; i < dutycount; i++ {
        ch
    }
}

2.タスクの数が一定でない場合
package main
 
import (
    "fmt"
)
 
func Afunction(routineControl chan int, feedback chan string) {
    defer func() {
        routineControl
        feedback "finish"
    }()
 
    // do some process
    // ...
}
 
func main() {
    var (
        routineCtl chan int    = make(chan int, 20)
        feedback   chan string = make(chan string, 10000)
 
        msg      string
        allwork  int
        finished int
    )
    for i := 0; i < 1000; i++ {
        routineCtl 1
        allwork++
        go Afunction(routineCtl, feedback)
    }
 
    for {
        msg = feedback
        if msg == "finish" {
            finished++
        }
        if finished == allwork {
            break
        }
    }
}

 
四、無限ループを使用してgoroutineが仕事を完了したかどうかをチェックしない
goroutineを使用する場合、私たちはよくこのようなコードを書きます.
package main
 
import (
    "fmt"
)
 
var (
    flag bool
    str  string
)
 
func foo() {
    flag = true
    str = "setup complete!"
}
 
func main() {
    go foo()
    for !flag {
        // ,foo() ,flag=true, 。
        // 
    }
    fmt.Println(str)
}

実行後、mainの無限ループは永遠に終了できないことが分かったので、Goではこのような無限ポーリングでgoroutineが完了したかどうかをチェックしないでください.
 
私たちはchannelを使用することで、foo()とmain()の通信を実現し、foo()の実行が完了した後、channelを通じてmain()にメッセージを送信し、自分のことが完了したことを伝え、main()がメッセージを受信した後、他の操作を継続することができます.
package main
 
import (
    "fmt"
)
 
var (
    flag bool
    str  string
)
 
func foo(ch chan string) {
    flag = true
    str = "setup complete!"
    ch "I'm complete." //foo(): , ~
}
 
func main() {
    ch := make(chan string)
    go foo(ch)
    //main():OK, ~
    for !flag {
    }
    fmt.Println(str)
}

 
 
 
 
 
この文書は次のとおりです.http://blog.csdn.net/gophers/article/details/24665419
転載先:https://www.cnblogs.com/liuzhongchao/p/9633814.html