前回Go
のインタフェースについて書いた。
kzkohashi.hatenablog.com
(アイキャッチが更新されてない・・)
今回はGo
の真髄とも言える、ゴルーチンとチャネルによる並行処理(Concurrent)について学んでいく。
並行(Concurrent)処理と並列(Parallel)処理
間違えやすいのだが、並行処理は並列処理と概念的に異なる。
こちらの記事がわかりやすかったので、引用させていただく。
Concurrent(並行)は「複数の動作が、論理的に、順不同もしくは同時に起こりうる」こと
Parallel(並列)は、「複数の動作が、物理的に、同時に起こること」
引用: freak-da.hatenablog.com
並行は1人の人が複数の仕事をし、並列は複数の人が複数の仕事をしている状態だ。
とはいっても、並行処理は擬似並列ともいえるので、日本語的には「並列処理」を使っても問題ないと思う。
この辺りも面白かった。
興味がある人は以下を読むと良さそう。
自分は近々届くので、また別途まとめたい。
www.oreilly.co.jp
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
(スレッド)間で値の受け渡しを安全にできるので、とても簡単に扱える。
qiita.com
func main() {
c := make(chan string, 2)
c <- "hello"
c <- "hello2"
fmt.Println(<-c)
fmt.Println(<-c)
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(url string, resultUrl ResultUrl) (body string, urls []string, err error)
}
type ResultUrl struct {
v map[string]int
}
func checkUrl(c *ResultUrl, key string) bool {
return c.v[key] == 1
}
func Crawl(url string, depth int, fetcher Fetcher, c ResultUrl, quit chan bool) {
TODO
TODO
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
}
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)
}
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
サーバー作ることをしようかな。