goroutineはスレッドのようなものです。
joinはないので、終了を待つ場合はchannelを使います。
<-quit
とすると、quitというchannelに値が書き込まれるまで待ちます。
私の環境では、
Create goroutine
Waiting for the goroutine to complete
Start goroutine
End goroutine
Test compleated
という順番で表示されました。WaitingとStartの順番が直感と合いませんが、 goroutineの起動にオーバヘッドがあるのでしょうかね。
package main
import "fmt"
import "time"
func main() {
fmt.Println("Create goroutine")
quit := make(chan bool)
go func() {
fmt.Println("Start goroutine")
time.Sleep(3 * time.Second)
fmt.Println("End goroutine")
quit <- true
}()
fmt.Println("Waiting for the goroutine to complete")
<-quit
fmt.Println("Test compleated")
}
goroutineは外部宣言したfuncでも起動できます。 channelが必要な場合は引数にchannelを渡します。 もちろん、channel以外の引数も渡すことができます。
Start goroutine - Apple 10
と表示されます。
package main
import "fmt"
import "time"
func main() {
fmt.Println("Test start")
fmt.Println("Create goroutine")
quit := make(chan bool)
go appleGoroutine("Apple", 10, quit)
fmt.Println("Waiting for the goroutine to complete")
<-quit
fmt.Println("Test compleated")
}
func appleGoroutine(fruit string, a int, quit chan bool) {
fmt.Printf("Start goroutine - %s %d\n", fruit, a)
time.Sleep(3 * time.Second)
fmt.Println("End goroutine")
quit <- true
}
終了待ちはchannelの受信で行います。goroutineを生成する を参照してください。
スレッドと違ってgoroutineは外部から終了させることは出来ません。 チャネルをつかって無限ループを抜けるようにするのが一案です。 無限ループを抜ける際に、breakにラベルを与えてどの階層まで抜けるかを指示できるようです。
下記のプログラムはGorutine内で"."を100msec置きに50回表示して終了しますが、 "."を押すとGorutineを途中で終了します。終了の意思を伝えるためにkillというチャネルで 通信を行っています。
なお、一文字入力を受け付けるのに go-ibgetkeyという ライブラリを使いました。
package main
import "fmt"
import "github.com/tlorens/go-ibgetkey"
import "time"
func main() {
kill := make(chan bool)
finished := make(chan bool)
go killableGoroutine(kill, finished)
targetkey := "."
t := int(targetkey[0])
loop:
for {
input := keyboard.ReadKey()
select {
case <-finished:
break loop
default:
if input == t {
kill <- true
break loop
}
}
}
}
func killableGoroutine(kill, finished chan bool) {
fmt.Println("Started goroutine. Push \".\" to kill me.")
for i := 0; i < 50; i++ {
select {
case <-kill:
fmt.Println()
fmt.Println("Killed")
finished <- true
return
default:
fmt.Print(".")
time.Sleep(100 * time.Millisecond)
}
}
fmt.Println()
fmt.Println("Finished..push any key to abort.")
finished <- true
return
}
goroutineの実行を終了させると同様にchannelで通信を行います。
下記のプログラムはGorutine内で"."を100msec置きに50回表示して終了しますが、 "."を入力するとGorutineを途中で停止・再開します。
package main
import "fmt"
import "github.com/tlorens/go-ibgetkey"
import "time"
func main() {
com := make(chan string)
finished := make(chan bool)
go stoppableGoroutine(com, finished)
targetkey := "."
t := int(targetkey[0])
running := true
loop:
for {
input := keyboard.ReadKey()
select {
case <-finished:
break loop
default:
if input == t {
if running == true {
com <- "stop"
running = false
} else {
com <- "start"
running = true
}
}
}
}
}
func stoppableGoroutine(command chan string, finished chan bool) {
fmt.Println("Started goroutine. Push \".\" to stop/start me.")
running := true
i := 0
for i < 50 {
select {
case com := <-command:
if com == "stop" {
running = false
} else {
running = true
}
default:
}
if running == true {
fmt.Print(".")
time.Sleep(100 * time.Millisecond)
i++
}
}
fmt.Println()
fmt.Println("Finished..push any key to abort.")
finished <- true
return
}
Ruby版ではリストを出してそれぞれをkillするというプログラムでしたが、 そもそもkillを簡単に実装する方法がないので、リスト表示だけにとどめます。 goroutineをリスト化する関数自体やmain関数自身もgoroutineなので 多数のgoroutineが表示されます。
package main
import "fmt"
import "os"
import "runtime/pprof"
import "time"
func main() {
go goroutine1()
go goroutine2()
time.Sleep(1 * time.Second) // goroutineの起動のオーバヘッド待ち
pprof.Lookup("goroutine").WriteTo(os.Stdout, 2) // 2はデバッグレベル。goroutineだけリストする、の意味。
}
func goroutine1() {
time.Sleep(5 * time.Second)
fmt.Println("Goroutine1 finished")
}
func goroutine2() {
time.Sleep(5 * time.Second)
fmt.Println("Goroutine2 finished")
}
Ruby版ではQueueを使っているところを、channelに変更します。 makeでchannelの深さを指定しますが、深さ以上に値を突っ込もうとすると その時点でロックします。こちらをご参考。
下記プログラムはキー入力を受付け、平方根を返します。-1を入力すると終了します。
package main
import "fmt"
import "math"
func main() {
queue := make(chan int, 3) // 3はキューの深さ
go sqrtGoroutine(queue)
line := 0
loop:
for {
fmt.Scanln(&line)
if line == -1 {
break loop
} else {
queue <- line
}
}
}
func sqrtGoroutine(queue chan int) {
for {
n := <-queue
if int(n) >= 0 {
val := math.Sqrt(float64(n))
fmt.Printf("Square(%d) = %f\n", int(n), val)
} else {
fmt.Println("?")
}
}
}
syncパッケージにMutexがあります。複数のgoroutine終了を待つにはWaitGroupを使います。 こちらがよくまとまっています。
ファイルcount.txtを作って0を書き込み、gotoutine内ではファイルの数値を読んでインクリメントして書き戻します。 終了後は、goroutineが10個走るのでcount.txtの値は10になっています。
package main
import "fmt"
import "sync"
import "io/ioutil"
func main() {
wg := new(sync.WaitGroup)
m := new(sync.Mutex)
write(0)
for i := 0; i < 10; i++ {
wg.Add(1)
go countupGoroutine(wg, m)
}
wg.Wait()
}
func countupGoroutine(wg *sync.WaitGroup, m *sync.Mutex) {
m.Lock()
defer m.Unlock()
counter := read() + 1
write(counter)
wg.Done()
}
func write(i int) {
s := strconv.Itoa(i)
ioutil.WriteFile("count.txt", []byte(s), os.ModePerm)
}
func read() int {
t, _ := ioutil.ReadFile("count.txt")
i, _ := strconv.Atoi(string(t))
return i
}