こんにちは、ナナオです。
Goroutines
goroutine(ゴルーチン)はGoのランタイムで管理される軽量なスレッドです。
複雑な記法は必要ありません。goキーワードを関数実行時の先頭に入れるだけです。
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
Channels
チャネル型(Channel)は、チャネルオペレータ(<-)を使って値の送受信を行うことができます。
これを使うことでゴルーチンの値を簡単に取得することができます。
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
selectステートメント
selectステートメント、これ最初に見たときは一見意味が分かりませんでした。
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
ここで理解できなかったのは以下の点です。
- cがラムダ関数内でPrintlnされているが、なぜこれで出力されるのか?
- quitにラムダ関数内で値を入れているが、なぜこれで実行終了と判定されるのか?
cがラムダ関数内でPrintlnされているが、なぜこれで出力されるのか?
まず、fibonacci関数が c <- x(送信)しようとしますが、最初は受け取り手がいないので、ここで一旦停止します。
一方でmain関数内で実行されるゴルーチンの関数はfmt.Println(<-c)を実行しようとします。
ここで送り手と受け手がつながります。
つながった瞬間にxの値がチャネルを通ってPrintlnで出力されます。
送信が完了したらx, y = y, x+yを実行します。
quitにラムダ関数内で値を入れているが、なぜこれで実行終了と判定されるのか?
上記の送り手と受け手の概念を理解すれば、こちらも同じような挙動になっていることが分かります。
ゴルーチンの中のforループが終わったらquitにチャンネルで値が送られます。
するとfibonacci関数のselect内のcase <- quitが準備完了の状態になります。
selectはquitから値を受け取り、fibonacci関数は終了します。
うーん、でもなんかまだ感覚的には理解できないなぁ。
Exercise: Equivalent Binary Trees
エキササイズを解いてみました。
(AIに助けてもらいながらではありますが)
package main
import (
"fmt"
"golang.org/x/tour/tree"
)
// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
if t == nil {
return
}
Walk(t.Left, ch)
ch <- t.Value
Walk(t.Right, ch)
}
// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {;
ch1, ch2 := make(chan int), make(chan int)
go func() {
Walk(t1, ch1)
close(ch1)
}()
go func() {
Walk(t2, ch2)
close(ch2)
}()
for {
// カンマ ok 形式で「値」と「チャネルが開いているか」を受け取る
v1, ok1 := <-ch1
v2, ok2 := <-ch2
// 両方が閉じたら、最後まで不一致がなかったということ
if !ok1 && !ok2 {
break
}
// どちらか一方が先に閉じた、または値が違う場合は false
if v1 != v2 || ok1 != ok2 {
return false
}
}
return true
}
func main() {
ch := make(chan int)
// ラップしたゴルーチン内で実行
go func() {
Walk(tree.New(1), ch)
close(ch) // 送信が終わったら、ここで閉じる!
}()
for i := range ch {
fmt.Println(i)
}
result := Same(tree.New(1), tree.New(1))
fmt.Println("result: ", result)
}
まとめ
今日はこの辺でおわり!