質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.47%
Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

Q&A

解決済

1回答

2099閲覧

A Tour of Go のChannelsについて

mask_mus

総合スコア37

Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

0グッド

0クリップ

投稿2020/10/05 06:38

A Tour of GoのChannelsについての質問です。
デフォルトのコードを実行すると
-5 17 12
という出力結果が得られるのですが、channelに返される順番としてなぜこの順番になるのでしょうか?配列の前半を引数として実行した関数が最初に処理されて、先に書いたxの方に結果が渡ると思ったのですが、結果は予想と逆でした。

また、次のようにコードを改変して実行したら、
4 9 -1 12
という出力になりました。channelはスタックになっているのかと思ったのですが、そうではありませんでした。channelが返される順番はどのように決定されるのでしょうか。

Go

1package main 2 3import "fmt" 4 5func sum(s []int, c chan int) { 6 sum := 0 7 for _, v := range s { 8 sum += v 9 } 10 c <- sum // send sum to c 11} 12 13func main() { 14 s := []int{7, 2, 8, -9, 4, 0} 15 16 c := make(chan int) 17 go sum(s[:len(s)/3], c) 18 go sum(s[len(s)/3:len(s)*2/3], c) 19 go sum(s[len(s)*2/3:], c) 20 x, y, z := <-c, <-c, <-c// receive from c 21 22 fmt.Println(x, y, z, x+y+z) 23} 24

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

chanはメッセージキューなのでFIFO(先入先出)と呼ばれる挙動になります。
スタックはLIFO(後入先出)と呼ばれる挙動です。

ただし、質問にあるコードの場合、順番が入れ替わってもおかしくないかもしれません。
(順番が維持される保証のない記述なので)

追記

以下のコードを何度も手元で実行した出力を下に貼ります。
https://play.golang.org/p/udCf_zvmaF3

go

1package main 2 3import "fmt" 4 5func hey(n int, c chan int) { 6 fmt.Println("hey:", n) 7 c <- n 8} 9func main() { 10 c := make(chan int) 11 fmt.Println("setup") 12 go hey(1, c) 13 go hey(2, c) 14 go hey(3, c) 15 fmt.Println("started") 16 x, y, z := <-c, <-c, <-c // receive from c 17 18 fmt.Println(x, y, z, x+y+z) 19}

出力結果

shell

1$ go run . 2setup 3started 4hey: 3 5hey: 2 6hey: 1 73 2 1 6 8$ go run . 9setup 10started 11hey: 3 12hey: 1 13hey: 2 143 1 2 6 15

goステートメントが3行を通り抜けてからhey関数が呼ばれることがわかります。
そして、このように実行のたびに実行される順番が異なっていることが確認できます。
つまり、goroutineの準備が完了した状態で次にgoroutineスイッチが起こる時
待機しているgoroutine群のどれが起動するのかは不定つまりランダムです。

このような挙動はある種狙って組み込まれています。

複数のgoroutineがあるチャネルからの取り出しを待っている状態で一つだけ値をそのチャネルに書き込んだ場合、
どのチャネルの取り出し待ちが発火するかはランダムです。

ランダムにする理由は仕事をするgoroutineを分散させるという狙いがあります。
(特定のgoroutineだけ働いている状態を回避する)

go

1x, y, z := <-c, <-c, <-c

ここは最初xだけがチャネルからの取り出し待ちになります。
xが値を受け取ったらyがチャネルからの取り出し待ちになります。
yが値を受け取ったら最後にzがチャネルからの取り出し待ちになります。
ここはシーケンシャルに処理されることは保証されるということです。
(3行に分けて書いても同様です)

投稿2020/10/05 08:08

編集2020/10/05 11:55
nobonobo

総合スコア3367

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mask_mus

2020/10/05 09:27

上の行に書いたものから順にキューに入っていくと思っていたのですが、行の順番は関係ないのでしょうか?キューに入る順番はどのようにして決まるのでしょうか?
nobonobo

2020/10/05 09:40

chanに投げ込んだ順番の通りに取り出しされるのは保証されますが、質問のコードはchanに投げ込まれる順番は予想と異なることがありうる書き方になってます。
mask_mus

2020/10/05 09:49

自分なりに考察した結果、s[len(s)/3:len(s)*2/3]等のインデックスの処理時間と、sum関数内のループ処理速度の差によって順番と異なるのではないかと考えたのですか、どのくらい合っているのでしょうか?出来れば、なぜ予想と異なる順番になってしまうのかを教えていただきたいです。
nobonobo

2020/10/05 11:57 編集

処理内容によってチャネルへの書き込みに到達する時間が異なるという考えは正しいですが、 それよりも3つ程度のgoroutineセットアップは 実際のgoroutineの処理を開始するまでもなく準備完了してしまいますので、 その後3つのgoroutineのいずれかが起動しますが、 走る準備を終えた3つのgoroutineが実際の処理を開始する順番は保証されません。
nobonobo

2020/10/05 11:56

実例を追記しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.47%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問