前回Go
のインタフェースについて書いた。
kzkohashi.hatenablog.com (アイキャッチが更新されてない・・)
今回はGo
の真髄とも言える、ゴルーチンとチャネルによる並行処理(Concurrent)について学んでいく。
並行(Concurrent)処理と並列(Parallel)処理
間違えやすいのだが、並行処理は並列処理と概念的に異なる。
こちらの記事がわかりやすかったので、引用させていただく。
Concurrent(並行)は「複数の動作が、論理的に、順不同もしくは同時に起こりうる」こと Parallel(並列)は、「複数の動作が、物理的に、同時に起こること」
引用: freak-da.hatenablog.com
並行は1人の人が複数の仕事をし、並列は複数の人が複数の仕事をしている状態だ。
とはいっても、並行処理は擬似並列ともいえるので、日本語的には「並列処理」を使っても問題ないと思う。
この辺りも面白かった。
興味がある人は以下を読むと良さそう。
自分は近々届くので、また別途まとめたい。
Goroutineで並行処理を行う。
Goroutine
の実装は簡単で、関数の前にgo
をつけるだけだ。
これだけで並行処理がとりあえずは実装できる。
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") }
引用: https://go-tour-jp.appspot.com/concurrency/1
並行処理のプログラミングをする際は、main
での処理とは別に処理が走ってしまうので、Channel
をうまく使って制御したり、Sleep
で待つという方法もある。
Sleep
は処理が終わる終わらないに限らず、その時間しかまたないのでよくない。
Channelを使う
A Tour Go
をやってもあまり理解しなかったが、以下の記事がよかったのでこちらを見た方がいい。
channel
は値をやり取りをするためのキューみたいなもんだ。しかも、goroutine
(スレッド)間で値の受け渡しを安全にできるので、とても簡単に扱える。
func main() { // 容量2のchannel型で作成 c := make(chan string, 2) // データを入れる c <- "hello" // データを入れる c <- "hello2" // c <- "hello3" 容量2のため3個目をいれたらエラー // 最初に入れたデータをchannelから取り出す fmt.Println(<-c) fmt.Println(<-c) // fmt.Println(<-c) 3つ目はない c = make(chan string, 1) c <- "world" fmt.Println(<-c) }
Exercise: Web Crawler を解く
A Tour Go
の最後に、goroutine
の問題があったので解いて見た。
go-tour-jp.appspot.com
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, resultUrl ResultUrl) (body string, urls []string, err error) } type ResultUrl struct { v map[string]int } // Value returns the current value of the counter for the given key. func checkUrl(c *ResultUrl, key string) bool { return c.v[key] == 1 } // Crawl uses fetcher to recursively crawl // pages starting with url, to a maximum of depth. func Crawl(url string, depth int, fetcher Fetcher, c ResultUrl, quit chan bool) { // TODO: Fetch URLs in parallel. // TODO: Don't fetch the same URL twice. // This implementation doesn't do either: if depth <= 0 { quit <- true return } if checkUrl(&c, url) { quit <- true return } body, urls, err := fetcher.Fetch(url, c) if err != nil { fmt.Println(err) quit <- true return } fmt.Printf("found: %s %q\n", url, body) quit2 := make(chan bool) for _, u := range urls { go Crawl(u, depth-1, fetcher, c, quit2) <-quit2 } quit <- true return } func main() { c := ResultUrl{v: make(map[string]int)} quit := make(chan bool) go Crawl("https://golang.org/", 4, fetcher, c, quit) <-quit } // 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, c ResultUrl) (string, []string, error) { c.v[url] = 1 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/", }, }, }
すでにクロールしたURL
を判定する処理に、sync.Mutex
を最初入れてたが、入れなくてもできたのでちょっと怪しい。
キモとしては、channel
型のquit
に値を入れ、<-quit
でgoroutine
の処理を待つことができる。
終わりに
Go
の勉強を駆け足でやってみた。
最初は意味がわからなかったが、毎日少しずつ見ていくと慣れていくもんだな。
次からはGo
でAPI
サーバー作ることをしようかな。