これをやる
Goroutines
go f(x)
で別スレッド関数を実行できる。
Goroutinesは同じアドレス空間で動くので、共有メモリへのアクセス時は同期する必要がある。
package main import ( "fmt" "time" ) type Test struct { value int } func (test *Test) add(v int) { test.value = test.value + v } func add100(test *Test) { for i := 0; i < 100; i++ { test.add(1) } } func main() { v := Test{0} go add100(&v) go add100(&v) go add100(&v) time.Sleep(10 * time.Millisecond) fmt.Println(v) }
これおかしなことになるかと思ったけどなんか毎回300で出力される。
Channels
package main import "fmt" func test(c chan int) { c <- 1 } func main() { c := make(chan int) go test(c) x := <-c // ここで同期する fmt.Println(x) }
- 別のスレッドから値を受け取る
- 受信側が同期する
ための機能という理解でいいのだろうか。
package main import "fmt" import "time" func test(c chan int) { c <- 1 time.Sleep(10000) fmt.Println("wow") } func main() { c := make(chan int) go test(c) x := <-c // ここで同期する fmt.Println(x) }
こうすると"wow"が出力されないのはなんでだろう。 同期するのはchannelsの受信についてだけで関数については同期せず、main関数が終わるとスレッドごと破棄されるのかな。
package main import "fmt" import "time" func test(c chan int) { time.Sleep(10000) fmt.Println("wow") c <- 1 } func main() { c := make(chan int) go test(c) x := <-c // ここで同期する fmt.Println(x) }
これは"wow"が出力されるので前者は正しそう。
Channelsのバッファ
Channelsはバッファをもてる。Channelsにバッファよりも多く値を送信するとエラー。
package main import "fmt" func test(c chan int) { c <- 1 } func main() { ch := make(chan int, 1) go test(ch) go test(ch) go test(ch) x, y, z := <-ch, <-ch, <-ch fmt.Println(x, y, z) }
なぜこれはエラーにならないのだろう。
package main import "fmt" func main() { ch := make(chan int, 1) ch <- 1 ch <- 1 ch <- 1 x, y, z := <-ch, <-ch, <-ch fmt.Println(x, y, z) }
これはエラーになる。関数呼び出しの時にチャネルがコピーされて別の値になる的な?
package main import "fmt" func test(c *chan int) { *c <- 1 } func main() { ch := make(chan int, 1) go test(&ch) go test(&ch) go test(&ch) x, y, z := <-ch, <-ch, <-ch fmt.Println(x, y, z) }
ポインタは別になるようだがこれでもやはりエラーにならない。
package main import "fmt" func test(c chan int) { for i := 0; i < 3; i++ { c <- 1 } close(c) } func test2(c chan int){ v, ok := <-c fmt.Println(v,ok) } func main() { ch := make(chan int, 4) go test(ch) test2(ch) test2(ch) test2(ch) test2(ch) // 0 false }
channelsに値は3回しか送られてこないが、closeによって待機を打ち切れる。
package main import "fmt" func test(c chan int) { for i := 0; i < 3; i++ { c <- 1 } close(c) } func main() { ch := make(chan int, 4) go test(ch) for i := range ch { fmt.Println(i) } }
rangeでchannelsが閉じるまで値を受けられる。
Select
受信時のSelect
package main import ( "fmt" "time" ) func test(c1, c2 chan int) { c1 <- 1 time.Sleep(1000) fmt.Println("send 1") c2 <- 2 fmt.Println("send 2") } func main() { c1 := make(chan int) c2 := make(chan int) go test(c1, c2) for { select { case x := <-c1: // c1が送信されたら受信しつつ実行 fmt.Println(x) case y := <-c2: // c2が送信されたら受信しつつ実行 fmt.Println(y) return } } }
複数のチャネルを同期できる。
送信時のSelect
package main import ( "fmt" "time" ) func test(c1, c2 chan int) { for { select { case c1<-1: // c1を受信されたら送信しつつ実行 fmt.Println("receive 1") case c2<-2: // c2を受信されたら送信しつつ実行 fmt.Println("receive 2") return } } } func main() { c1 := make(chan int) c2 := make(chan int) go test(c1, c2) fmt.Println(<-c1) fmt.Println(<-c2) time.Sleep(1000) }
Defaultによる待機
package main import ( "fmt" "time" ) func test(c1, c2 chan int) { c1 <- 1 time.Sleep(1000) fmt.Println("send 1") c2 <- 2 fmt.Println("send 2") } func main() { c1 := make(chan int) c2 := make(chan int) go test(c1, c2) for { select { case x := <-c1: // c1が送信されたら受信しつつ実行 fmt.Println(x) case y := <-c2: // c2が送信されたら受信しつつ実行 fmt.Println(y) return default: fmt.Println("not received") // 待機中に実行される } } }
Exercise: Equivalent Binary Trees
package main import "golang.org/x/tour/tree" import "fmt" // Walk walks the tree t sending all values // from the tree to the channel ch. func Walk(t *tree.Tree, ch chan int) { waking(t, ch) close(ch) } func waking(t *tree.Tree, ch chan int) { if t.Left != nil { waking(t.Left, ch) } ch <- t.Value if t.Right != nil { waking(t.Right, ch) } } // Same determines whether the trees // t1 and t2 contain the same values. func Same(t1, t2 *tree.Tree) bool { ch1 := make(chan int) ch2 := make(chan int) go Walk(t1, ch1) go Walk(t2, ch2) for value1 := range ch1 { value2 := <-ch2 if value1 != value2 { return false } } _, ok := <-ch2 if ok { return false } return true } func main() { ch := make(chan int) // Walk test go Walk(tree.New(1), ch) for v := range ch { fmt.Println(v) } // Same test fmt.Println(Same(tree.New(1), tree.New(1))) fmt.Println(Same(tree.New(1), tree.New(2))) fmt.Println(Same(tree.New(2), tree.New(1))) }
A Tour of Go. Exercise: Web Crawler
URLのプールがあって非同期でそっからランダムにフェッチ、みたいな路線で考えてたんだけどよくわかんなくなった。
で、これを半日眺めて自分でも書いて見たらまるっきり同じになってしまった。 A Tour of Go. Exercise: Web Crawler · GitHub
外側のループがfetchの回数とチャネルで結果を受け取る回数を同期させるためのものだと気づくのにやたら時間がかかった。
package main import ( "fmt" ) type Fetcher interface { // Fetch returns the body of URL and // a slice of URLs found on that page. Fetch(url string) (body string, urls []string, err error) } type Result struct { depth int urls []string url string body string err error } // Crawl uses fetcher to recursively crawl // pages starting with url, to a maximum of depth. func Crawl(url string, depth int, fetcher Fetcher) { ch := make(chan Result, 1) crawledUrls := make(map[string]bool) crawledUrls[url] = true // 重複回避用 go fetch(url, 0, ch) // fetchした回数と同じだけループ for i := 1; i > 0; i-- { r := <-ch // なんらかのスレッドがfetchに完了するのを待機 if r.err != nil { fmt.Println(r.err) continue } fmt.Printf("found: %s %q\n", r.url, r.body) if r.depth >= depth { // 最大のdepthまで行ったら次はクロールしない continue } for _, u := range r.urls { if !crawledUrls[u] { crawledUrls[u] = true // ここは同期的に動いてるのでmapは壊れない go fetch(u, r.depth+1, ch) i++ // fetchする回数をカウントし、ループとfetchの回数を合わせる } } } return } func fetch(url string, depth int, ch chan Result) { body, urls, err := fetcher.Fetch(url) ch <- Result{depth, urls, url, body, err} } func main() { Crawl("https://golang.org/", 4, fetcher) } // fakeFetcher is Fetcher that returns canned results. type fakeFetcher map[string]*fakeResult type fakeResult struct { body string urls []string } func (f fakeFetcher) Fetch(url string) (string, []string, error) { if res, ok := f[url]; ok { return res.body, res.urls, nil } return "", nil, fmt.Errorf("not found: %s", url) } // fetcher is a populated fakeFetcher. var fetcher = fakeFetcher{ "https://golang.org/": &fakeResult{ "The Go Programming Language", []string{ "https://golang.org/pkg/", "https://golang.org/cmd/", }, }, "https://golang.org/pkg/": &fakeResult{ "Packages", []string{ "https://golang.org/", "https://golang.org/cmd/", "https://golang.org/pkg/fmt/", "https://golang.org/pkg/os/", }, }, "https://golang.org/pkg/fmt/": &fakeResult{ "Package fmt", []string{ "https://golang.org/", "https://golang.org/pkg/", }, }, "https://golang.org/pkg/os/": &fakeResult{ "Package os", []string{ "https://golang.org/", "https://golang.org/pkg/", }, }, }
他の人の回答もいくつか見てみようと思うがとりあえず一旦A Tour of Goは終わりにする。 なんか作ろうとも思ったけど
『Goならわかるシステムプログラミング』 – 技術書出版と販売のラムダノート
ということで他に読んでない本をとりあえず消化しつつGoについては Go: The Complete Developer's Guide (Golang) | Udemy を一通り見て基礎的なところをある程度体系的にこなしていこうかなと思っている次第。
これはudemy見終わったらなんかあるあるなアルゴリズムを自分で実装してみる会をやろうかな。