はじめに

前回の記事の続きです。
前回の記事はこちら

マルチスレッド

今回はマルチスレッドをやります。

goroutine

かなり簡単な構文でスレッドが実現できるものらしいです。

1
go hello(a, b, c)

こんな感じで、 go キーワードを付けてます。
サンプルが用意されているので、早速実行してみます。
paiza-io-golang-01

処理順番だけ見ると、 go say("world") => say("hello") なので、 “world” が5回, “hello"が5回で連続して出力されるように思えます。
ただ、 say の中身を見てみると、 runtime.Gosched というものがあります。
これが

runtime.Gosched()ではCPUに時間を他の人に受け渡します。次にある段階で継続してこのgoroutineを実行します。 ということらしいです。
なので、CPUの時間を渡して、実行してを繰り返しているようですね。
なので、hello => world と交互に出力されているのだと思います。

channels

goroutine 間のデータの連携をできるようにする機構です。

1
2
3
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})

こんな感じで宣言していくようですね。

channelは<-演算子を使ってデータを送ったり受けたりします。
ch <- v // vをchannel chに送る。
v := <-ch // chの中からデータを受け取り、vに代入する。

なるほど~。ともあれサンプルを見てみます。
出力結果は下記のようになりました。
paiza-io-golang-02

プログラムを見ていくと、 変数c: chan intint型のチャネルをmakeしています。
それを goroutine として、 sum関数を2回呼んでいますね。
当然計算させる部分は異なっています。(ちょうど半分ずつ)
1回目は、0~2要素目まで
2回目は、3~5要素目まで
をそれぞれ計算しております。
なので、最後の fmt.Println(x, y, x + y) の結果は、
x: (-9) + 4 + 0 = -5
y: 7 + 2 + 8 = 17
x + y = 7 + (-2) + 8 + (-9) + 4 + 0 で 12 ですね。

おぉ!
goroutine を使って、別々に計算させた結果を受け取り合計を出せました!
これ、Javaとかだと結構つらめな処理を書いたりしないといけないので、かなーりわかりやすいですね。
もしかしたら、今のJavaは便利な機構が導入されてるかもなのでわかりませんが。

チャネルでgoroutine で計算した結果を受け取れるというのはわかりました.

デフォルトでは、channelがやり取りするデータはブロックされています。
もう片方が準備できていなければ、Goroutinesの同期はもっと簡単になります。
lockを表示する必要はありません。
いわゆるブロックとは、もし(value := <-ch)を読み取った場合、これはブロックされます。
データを受け取った段階で(ch<-5)を送信するいずれのものもデータが読み込まれるまでブロックされます。
バッファリングの無いchannelは複数のgoroutineの間で同期を行う非常に優れたツールです。

ただまだわかっていないのが、 go で読んだ関数に対して、 <-c, <-c で2回吐き出しているが、
これは順序が決まっているのでしょうか。
後は計算が終わった際の通知的なものや、fmt.Println(x, y, x + y) を実行する時点で計算が終わっていない場合はどうなっているのかも気になります。
このあたりはもう少し深く勉強しないといけなさそうです。

Buffered Channels

チャネルにバッファをもたせられる(大きさ指定の有無)

1
2
3
4
ch := make(chan type, value)

value == 0 ! バッファリング無し(ブロック)
value > 0 ! バッファリング(ブロック無し、value個の要素まで)

後ろに value をつければいいんですね~。
早速サンプルを実行してみました。
paiza-io-golang-03
paiza-io-golang-04
paiza-io-golang-05

Buffer 以上のデータを入れてしまうとエラーになってしまうようですね。

RangeとClose

上の例では、2回cの値を読み込む必要がありました。
これではあまり便利ではありません。
Goはこの点を考慮し、rangeによってsliceやmapを操作するのと同じ感覚でバッファリング型のchannelを操作することができます。下の例をご覧ください。

便利じゃなかった!

paiza-io-golang-06
フィボナッチ数列を計算しているようです。
<-c ではなくて、 range c で受けていますね。

for i := range cでこのchannelがクローズを明示されるまで連続してchannelの中のデータを読み込むことができます。上のコードでchannelのクローズが明示されているのが確認できるかと思います。

なんとなくわかりました。
channelがクローズされるまで、待ってくれる?みたいですね。

Select

複数のchannelがある場合に操作を補助してくれる?もののようです。

selectはデフォルトでブロックされます。channelの中でやりとりされるデータを監視する時のみ実行します。
複数のchannelの準備が整った時に、selectはランダムにひとつ選択し、実行します。

はい。 サンプルを実行してみます。
paiza-io-golang-07

今度は、 cquit という変数でchannel がありますね。
どうやら go func() の無名関数で、一緒に使われています。
順を追うと、 fibonnacci(c, func) go func() の中身が実行されて、

go func(): for 0~10まで回しつつ <-c でチャネルの値を受け取る。 fibonacci で、 select-casecase c <- x:case <-quit: を実行するのですが、

selectはデフォルトでブロックされます。
channelの中でやりとりされるデータを監視する時のみ実行します。
複数のchannelの準備が整った時に、selectはランダムにひとつ選択し、実行します。

なので、おそらく実行できる case c <- x が実行されると思います。 最終的に, quit <- 0のタイミングで、fibonacci の関数が return されるのかと思います。

(だいぶ、自分の理解が怪しい気がする。)

タイムアウト

select を使うことで、タイムアウトを設定できるようですね。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func main() {
    c := make(chan int)
    o := make(chan bool)
    go func() {
        for {
            select {
                case v := <- c:
                    println(v)
                case <- time.After(5 * time.Second):
                    println("timeout")
                    o <- true
                    break
            }
        }
    }()
    <- o
}

コードのサンプルは、<-quit の部分が、 <-time~ となっていますね。 この時間を過ぎたらこれが実行されて、関数が終了するみたいです。

runtime goroutine

頭の片隅にいれておきます。
まず、自分の理解ができてなさそう。

おわりに

スレッド、並列-並行プログラミングは難しいです。
処理がどうなっているのかとかを考えて追っていくのがけっこう大変です。
この前 オライリー・ジャパン様から発売されている、Goの並行処理プログラミングの本を見たので読んでみようかと思います。
web application を作れたら、、、ね。
次回はついに、Webの基礎 にいきます。 まとめは読むだけで記事にはしません。