同時実行とsychronizationの試み−第1部:最初のアプローチ
15935 ワード
このシリーズの意図は、ゴラン生態系における同時性と同期について何かを教えることです.いつものように、シンプルでわかりやすい説明をしたいと思います.
我々はカウンタを作成したいと仮定します.すでに我々のニーズを満たす非常に速い実現.
これを行うにはさまざまな方法があります.メモリの一部(変数)を共有して、他のORoutineを待つことができます.もう1つの解決策は、他のGoroutineを直接伝えることである.
これらはもちろん2つの中心的な原則です、そして、それは詳細に彼らを説明するために現在このポストの範囲を越えて行くでしょう、しかし、私はあなたがすでに彼らを知っているかもしれないと思います.
GOとGOについて話しているので、プログラミングのパラダイムとルールを念頭に置いておきます.
私は、上記の建築上の説明をグラフィックで要約しました.
トリッキーな部分は、2つの印刷Goroutinesの間のコミュニケーションです.順序が非常に高い優先順位を持っているので(このポストでないなら役に立たないでしょう).
我々がコミュニケーションについて話すとき、我々は常にチャンネルについて話します.
以下は既に問題を解決するための解決策です.
我々は他のデータを共有したくないので、ちょうど他のGoroutineに通知します.
この解決策の危険は、我々が労働者goroutines(AとB)をきれいにすることができないということです.
メインGoroutineが他のすべてのGoroutinesを返すとき、同様に殺されます.私たちの例ではあまり重要ではありません.
しかし、我々が何かをきれいにしたいという使用例が、あります.使用中にいくつかのファイルを閉じたり、ネットワーク接続を閉じたりしてください.
私たちがあなたの労働者Goroutinesのためにクリーンアップを可能にしたいとき、我々は標準のライブラリ
今、私たちは私たちの労働者Goroutinesのクリーンアップを有効にするためにwaitgroupsを追加している.
我々はカウンタを作成したいと仮定します.すでに我々のニーズを満たす非常に速い実現.
func main() {
i := 0
for {
i++
fmt.Println(i)
}
}
では、どのようにこの解決策をスケールするには?もちろん、並行した計算を使っているのですが、Goではスレッドについて話をしません.あなたは、数字を数えるより多くの仕事があると想像しなければなりません.何かかもしれませんが、結果は特定の順序でお願いします.これはこの例の挑戦的な部分です.我々は、同じGoroutinesを通して提供されるが、全く同じ出力を持ちたいです.// current and desired state would be
0, 1, 2, 3, 4, 5, 6, 7, ...
// but when just introducing concurrency,
// we could maybe end up like this
0, 2, 1, 3, 5, 4, 6, 7, ...
私たちが2匹のGoroutinesを産出して、彼らをカウントさせるとき、我々は上記のような出力を予想するかもしれません.我々はここで注文を伝えることができないので、ゴロゴネスはどうにかそれを知っていなければなりません.Goroutineコミュニケーション
これを行うにはさまざまな方法があります.メモリの一部(変数)を共有して、他のORoutineを待つことができます.もう1つの解決策は、他のGoroutineを直接伝えることである.
これらはもちろん2つの中心的な原則です、そして、それは詳細に彼らを説明するために現在このポストの範囲を越えて行くでしょう、しかし、私はあなたがすでに彼らを知っているかもしれないと思います.
GOとGOについて話しているので、プログラミングのパラダイムとルールを念頭に置いておきます.
Share memory by Communicating - A Golang core principle read more here
私は、上記の建築上の説明をグラフィックで要約しました.
トリッキーな部分は、2つの印刷Goroutinesの間のコミュニケーションです.順序が非常に高い優先順位を持っているので(このポストでないなら役に立たないでしょう).
解決策
我々がコミュニケーションについて話すとき、我々は常にチャンネルについて話します.
以下は既に問題を解決するための解決策です.
func main() {
// initialize all channels
printOdd := make(chan struct{})
printEven := make(chan struct{})
closer := make(chan struct{})
// spawn Goroutine A
go func() {
start := 0
// infinte looping
for {
// block until some data arrives from either channel
select {
case <-printEven:
// simulate the calculation
time.Sleep(time.Second)
// print
fmt.Println(start)
start = start + 2
// notify Goroutine B to print an even number now
printOdd <- struct{}{}
case <-closer:
return
}
}
}()
// spawn Goroutine B
go func() {
start := 1
for {
select {
case <-printOdd:
time.Sleep(time.Second)
fmt.Println(start)
start = start + 2
printEven <- struct{}{}
case <-closer:
return
}
}
}()
reader := bufio.NewReader(os.Stdin)
fmt.Println("Press enter to cancel")
fmt.Println("---------------------")
// trigger the ping-pong
printEven <- struct{}{}
// wait for console input to quit
reader.ReadString('\n')
fmt.Println("finished")
// we would like to let all other goroutines return, but in fact they starve away
// when the main goroutine returns
// closing this channel here is totally useless
close(closer)
}
空のstruct型は、メモリ最適化のために使用されます.我々は他のデータを共有したくないので、ちょうど他のGoroutineに通知します.
この解決策の危険は、我々が労働者goroutines(AとB)をきれいにすることができないということです.
メインGoroutineが他のすべてのGoroutinesを返すとき、同様に殺されます.私たちの例ではあまり重要ではありません.
しかし、我々が何かをきれいにしたいという使用例が、あります.使用中にいくつかのファイルを閉じたり、ネットワーク接続を閉じたりしてください.
クリーンアップ入門
私たちがあなたの労働者Goroutinesのためにクリーンアップを可能にしたいとき、我々は標準のライブラリ
sync.WaitGroup
のうちの1つを使用することができました.You may view the original documentation here: https://pkg.go.dev/sync/#WaitGroup
今、私たちは私たちの労働者Goroutinesのクリーンアップを有効にするためにwaitgroupsを追加している.
func main() {
printOdd := make(chan struct{})
printEven := make(chan struct{})
closer := make(chan struct{})
wg := sync.WaitGroup{}
go func() {
start := 0
wg.Add(1)
for {
select {
case <-printEven:
time.Sleep(time.Second)
fmt.Println(start)
start = start + 2
printOdd <- struct{}{}
case <-closer:
fmt.Println("finished odd printing")
wg.Done()
return
}
}
}()
go func() {
start := 1
wg.Add(1)
for {
select {
case <-printOdd:
time.Sleep(time.Second)
fmt.Println(start)
start = start + 2
printEven <- struct{}{}
case <-closer:
fmt.Println("finished even printing")
wg.Done()
return
}
}
}()
reader := bufio.NewReader(os.Stdin)
fmt.Println("Press enter to cancel")
fmt.Println("---------------------")
// trigger the ping-pong
printEven <- struct{}{}
reader.ReadString('\n')
fmt.Println("finished")
// we would like to let all other goroutines return
close(closer)
// panics, because a close on a channel may only be received once
// and therefore the call to wg.Done() is only called once instead of twice
wg.Wait()
// output: fatal error: all goroutines are asleep - deadlock!
}
現在いくつかの疑問が生じている.なぜ、新しいチャンネル(closer
)が他のものを返すように導入するでしょうか?我々は、ちょうどこれを達成するために我々の他のチャンネルを使用するかもしれません.しかし、これはフォローアップポストで議論する別のトリッキーな問題を紹介します.Edit: I mixed up even and odd - as pointed out in the comments
Reference
この問題について(同時実行とsychronizationの試み−第1部:最初のアプローチ), 我々は、より多くの情報をここで見つけました https://dev.to/davidkroell/go-concurrency-and-sychronization-part-1-first-approach-14l9テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol