Goroutineと
Channelから
はじめるGo言語
2013/9/28(土)
@初心者向けgolang勉強会
自己紹介
上田拓也
KLab株式会社
仕事:
 ・サーバサイド(PHP)
 ・フロントエンド(JS)
学生のころ:
 ・セルオートマトン
 ・複雑系
twitter : @tenntenn
アジェンダ
● Go言語とは
● GorotuineとChannel
Go言語とは?
Go言語ってなに?
● え?Go言語知らない?
● ビッグウェーブに乗り遅れてますよ!
Googleトレンド
Go言語とは?
● Googleが開発したコンパイラ言語
○ その他のGoogleが開発した言語
■ Dart : JSの代替
■ noop : JavaVMで動く言語(忘却のかなた)
● 設計者:
Robert Grisemer, Rob Pike, Ken Thompson
Go言語の特徴
● シンプルな文法/設計
○ 曖昧さを排除した設計
○ 強い静的型付け
○ 型推論ができるので、型を省略できる
○ ポインタはあるがポインタ演算はなし
○ 複数の戻り値を返す関数
○ ダックタイピング
○ ゼロ値初期化
● ゴールーチンとチャネル
● 豊富なライブラリ群
○ goツールは最強
○ Web系パッケージの豊富さ
○ Google App Engine for go
主なGo言語の勉強会@東京
● GoCon spring, autumn
○ Go言語の勉強会では一番規模の大きい
○ Go言語の中の人が来る
● 質実Go研
○ 標準ライブラリを中心にソースを読む会
○ 少人数で濃い話をする
● 電車でGo!
○ 電車に乗りながらGo言語をする
● Go羅温泉
○ 強羅温泉でGo言語をする
私とGo言語
● 研究で使うために始める
○ セルオートマトンシミュレータを作るため
● 主にGDG名古屋で活動していた
○ スタートGo #0, #1
● CodeIQにて問題を出題
● 社内でGo会を開催している
初心者向けの情報
● Tour of Go
○ Web上でGo言語の一通りいじれる
● Go言語の初心者が見ると幸せになれる場所
○ http://qiita.
com/tenntenn/items/0e33a4959250d1a55
045
Gorotuine
と
Channel
Concurrency is not Parallelism
● ConcurrencyとParallelismは違う by Rob Pike
○ Concurrency=並行?
○ Parallelism=並列?
● Concurrency
○ 同時にいくつかの事を扱う事
● Parallelism
○ 同時にいくつかの計算を行なう事
Concurrency is... by Rob Pike
Rob Pike氏のプレゼンのまとめでは、
○ Concurrency is powerful.
○ Concurrency is not parallelism.
○ Concurrency enables parallelism.
○ Concurrency makes parallelism (and scaling
and everything else) easy.
とある。
Gorotuineとは?
● Go言語ではゴールーチンを用いてConcurrencyを実
現している
● スレッドに似ている...
○ しかし、もっとCheap(=コストが低い)
○ LinuxやUnixのスレッドとは違う
■ スレッドの上にいくつも乗っている
● 簡単に作成できる
○ 関数呼び出しの前にgoキーワードを付ける
■ go f()
go + クロージャ
package main
import "fmt"
func main() {
go func() {
fmt.Println("別のゴールーチン")
}()
fmt.Println("mainゴールーチン")
}
並列度
● 並列度:GOMAXPROCS
○ 同時に最大で実行可能なCPU数
● デフォルトでは並列度は1になっている
○ runtime.NumCPU()で利用可能なCPU数が取得
できる
○ runtime.GOMAXPROCS()で並列度を設定する
【例】
runtime.GOMAXPROCS(runtime.NumCPU())
● 指定した並列度でゴールーチンを動かす
○ あまり増やしても意味がない
Goroutine間のデータのやりとりは?
Goroutine-1 Goroutine-2
go f1() go f2()
共有の変数を使う?
Goroutine-1 Goroutine-2
go f1() go f2()
変数v v = 100fmt.Println(v)
Goroutine間のデータのやりとり
● 共有の変数を使う?
func main() {
done := false
go func() {
time.Sleep(time.Second * 3)
done = true
}()
for !done {
time.Sleep(time.Millisecond)
}
}
競合が起きますよね!
Goroutine-1 Goroutine-2
go f1() go f2()
変数v v = 100v = 200
競合
Goroutine間のデータの競合
// これはダメ!!
package main
import "fmt"
import "time"
func main() {
n := 1
go func() {
for i := 2; i <= 5; i++ {
fmt.Println(n, "*", i)
n *= i
}
}()
// 続く
// 続き
for i := 1; i <= 10; i++ {
fmt.Println(n, "+", i)
n += 1
time.Sleep(500)
}
}
だめ!!
何が問題なのか?どう解決するの?
【問題】
● 同時に複数のGoroutineからアクセス
● 競合が起きる
【解決方法】
● 1つのGoroutineからのみアクセスする
● Channelを使う!
Channelを使う
Goroutine-1 Goroutine-2
go f1() go f2()
ch <- 100
Channel
<-ch
100
● Goroutine間でデータをやりとりするパイプ?のような
もの
Channelの特徴
● 送受信するデータの型が決まっている
○ 型を指定してChannelを作る
● 一度に保持できる容量がある
○ 初期化の際に容量を指定、デフォルトは0
● 送受信の際に相手の応答を待つ
○ 容量が0になると、受信元が受け取るまでブロックされる
送信のブロック
Goroutine-1 Goroutine-2
go f1() go f2()
ch <- 100
Channel
100
ブロック
● 相手が受信(<-ch)してくれるまで、次に進めない
受信のブロック
Goroutine-1 Goroutine-2
go f1() go f2()
Channel
100
ブロック
● 相手が送信(ch<-100)してくれるまで、次に進めない
<-ch
Channelの宣言方法と使い方
● 宣言
○ ch := make(chan 型) // 容量0
○ ch := make(chan 型, 容量)
● 送信
○ ch <- 100
○ ch <- "hoge"
■ 容量いっぱいであれば取り出されるの待機する
■ 容量0の場合は常に待つ
● 受信
○ n := <- ch
■ 送られてくるまで待機する
Channelの宣言方法と使い方
func main() {
done := make(chan bool) // 容量0
go func() {
time.Sleep(time.Second * 3)
done <- true
}()
<-done // 待つ
fmt.Println("done")
}
こういう場合はどうするの?
Goroutine-1
Goroutine-2
go f1() go f2()
Channel-1
● ブロックするなら複数のChannelからデータを受け取
れない!?
Goroutine-3
<-ch1
Channel-2
go f3()
<-ch2
ch1 <- 100
ch2 <- "hoge"
selectを使う
Goroutine-1
Goroutine-2
go f1() go f2()
Channel-1
● selectを使えば、同時に複数のchannelから受信でき
る
Goroutine-3Channel-2
go f3()
<-ch2
ch1 <- 100
ch2 <- "hoge"
select
<-ch1
selectを使う
● 先に受信可能になったcaseを実行する
// Goroutine-1にて
select {
case v1 := <-ch1:
// Goroutine-2から ch1 <- 100
case v2 := <-ch2:
// Goroutine-3から ch2 <- "hoge"
}
※Channelがnilだとそのcaseは無視:nil
channel
ReadOnlyとWriteOnly
● 通常のチャネルは双方向
○ 引数や戻り値で渡す場合は困る
func hoge(in chan int) {
n := <-in // 入力として使うのが正解!
in <- 100 // 間違った使い方ができる!
}
ReadOnlyとWriteOnly
● ReadOnly
○ in := make(<-chan int)
● WriteOnly
○ out := make(chan<- int)
● キャスト
○ 双方向チャネルはキャストでReadOnlyまたは
WriteOnlyに変化できる
○ ch := make(chan int)
○ in := (<-chan int)(ch)
○ out := (chan<- int)(ch)
ファーストクラスオブジェクト
● Channelはファーストクラスオブジェクト
○ 変数に入れれる
○ 引数に渡せる
○ 戻り値に取れる
○ ChannelのChannel
● 戻り値として、チャネルを返す関数は多用される
○ timeパッケージ
○ 5分間待つ
<-time.After(5 * time.Minute)
range
● for文のrangeで送られてくるデータを次々に取
り出せる
// 1分ごとに現在時間を出す
for now := range time.Tick(1 * time.Minute) {
fmt.Println(now)
}
どう使って行くのか?
● 沢山の独立したゴールーチンがチャネルを使っ
てやり取りをする!
○ Concurrencyになる
● ゴールーチンのコストは安いので、沢山作って
もそんなに問題ない
○ ※コストが0ではない
● for - selectパターン
for - selectパターン
Goroutine-1
Channel-1
Channel-2
select
for{}
Goroutine-2
for{}
Goroutine-3
for{}
● 各Goroutineが無限ループになっており、イベントリス
ナー的にChannelを使うパターン
Gopher君で表すとこんな感じ!
ひたすら本を入れる
ひたすら本を運ぶ
ひたすら台車を運ぶ
ひたすら本を燃やす
構成要素の局所的な振る舞いが
相互作用する事によって
複雑な現象を作り出す
複雑系みたいで楽しい!
まとめ
● GoroutineはConcurrencyを実現する機構
○ go f() で簡単に作れる
● ChannelはGoroutine間のデータのやり取りに
使う
○ 送受信のブロック
○ select〜case文
○ ファーストクラスオブジェクト
○ select - forパターン

Goroutineとchannelから始めるgo言語@初心者向けgolang勉強会