Go/並列処理

出典: フリー教科書『ウィキブックス(Wikibooks)』
< Go
ナビゲーションに移動 検索に移動

並列処理[編集]

基本[編集]

Go言語で、並列処理のようなものをしたい場合、Go言語が「ゴルーチン」(1文字目がガギグゲゴの「ゴ」)と名乗っているシステムを使うと、容易にコードを記述できます。よその言語でいう「スレッド」あるいは「コルーチン」(1文字目がカキクケコの「コ」)とも言われるものですが、そもそも「スレッド」自体、プログラム言語ではあまり一般的な機能ではないので、いまここで「ゴルーチン」というものの仕組みを新規に覚えましょう。

用途としては、ひとことでいうと、「デッドロック」回避ですが、そもそも「デッドロック」を知らない読者もいるので、あとで説明します。

標準C言語のような古めの関数には、このコルーチンのような機能が無いのです。


用途

標準C言語などのようなコルーチンのない古めの言語と比較して、Go言語のゴルーチンをもちいる利点としては、もしコード内で待機時間の長くなりそうな関数が(コード中に)いくつかある場合に、もしゴルーチンで関数を使えば、関数の呼出しの順序にかかわらず、先に短時間で終わりそうな関数から優先したりして、うまい順序で実行してくれます。


ゴルーチンの利点として、そのような場合のコードが短く簡潔になります。プログラマーがいちいち、時間を計測するコードを書いたりする手間が省けます。また、if文などを用いて待機から抜け出したりするコードを書いたりする手間が省けます。

なので、w:デッドロックのような現象も効率的に回避できるでしょう。デッドロックとは、2つ以上のスレッドが、設計ミスによって、お互いに相手のスレッドの終了を待ってしまう設計をしてしまい、そのためコードがいつまでも終了しない種類のバグのことです。


ゴルーチンの用途では前提として、複数個の関数の順序を工夫して実行するので、もし関数がそもそも複数個ないコードと、せっかくゴルーチンを使っても、ほぼ無意味になります。


呼び出し方

ゴルーチンは、関数を呼び出すさいに、その関数の前に go をつけて

go 関数名()

と呼び出せば、その関数がゴルーチンとして呼び出されます。


コード解説(下記に後述)

なお、下記コードでは、組み込み関数のSleepを使っています。これは指定した時間だけ一時待機する組み込み関数です。

なお、main関数の最後のほうには、多めの待機時間が必要です。


なぜなら、ゴルーチンの実行終了であっても、もし特にmain関数に待機命令を書かない限り、Go言語はそのまま先にmain関数のさいごまで到達してしまい、そのせいでプログラムが終了する仕組みだからです。

Go言語のゴルーチンの仕様は、たとえゴルーチンの並列実行中であっても、main関数の最後まで来ると、Go言語は強制的にそのプログラムを終了させる仕組みになっています。


コード例
package main

import (
    "fmt"
    "time"
)

func main() {
    go sleepTest()
    houkoku()
    time.Sleep(10 * time.Second )
}


func sleepTest(){
    fmt.Println("スリープ前" )
    time.Sleep(3 * time.Second )
    fmt.Println("スリープ終了" )

}

func houkoku() {

fmt.Println("報告" )


}


実行例
報告
スリープ前
スリープ終了


ゴルーチンの解説

main関数では先にスリープを始めてるのにかかわらず、 次に書かれたhoukoku 関数を先に実行していることが分かります。


このように並列的に実行してくれるので、時間のかかる処理を後回しにでき、効率化が望めます。



例2

では、別の例として、houkoku関数にも、Sleep を数秒ほど入れてみましょう。

package main

import (
    "fmt"
    "time"
)

func main() {
    go sleepTest()
    houkoku()
    time.Sleep(15 * time.Second )
}


func sleepTest(){
    fmt.Println("スリープ前" )
    time.Sleep(10 * time.Second )
    fmt.Println("スリープ終了" )

}

func houkoku() {
    time.Sleep(3 * time.Second )
    fmt.Println("報告" )

}


実行例
スリープ前
報告
スリープ終了


ゴルーチンの解説

このように sleepTestが先に実行されたにも拘わらず、待機時間の掛かる「スリープ終了」の表示の実行は後回しになっており、先に「報告」が表示されます。


無名関数[編集]

Go言語のゴルーチンは、main関数とは独立した関数にしなくても、main関数など既存の関数のブロック中に、ゴルーチンを書くこともできます。

その際、ブロック中のユーザ定義関数には、名前をつけなくなります。

このような、名前のないユーザ定義関数を無名関数(むめいかんすう)と言います。

なお、下記コードの「func」は関数名ではなく、演算子です。

書式
    go func() {
        // 処理の内容
    } () 

無名関数の { } ブロックを抜けた最後に () がつくのを忘れないように。


コード例
package main

import (
    "fmt"
    "time"
)

func main() {

    go func() {
        fmt.Println("スリープ前" )
        time.Sleep(10 * time.Second )
        fmt.Println("スリープ終了" )
    } () 

    houkoku()
    time.Sleep(15 * time.Second )
}


func houkoku() {
    time.Sleep(3 * time.Second )
    fmt.Println("無名関数の報告" )

}


実行例
スリープ前
無名関数の報告
スリープ終了