Upload
takuya-ueda
View
4.986
Download
1
Embed Size (px)
Citation preview
GoroutineとChannelからはじめるGo言語ver. 52015/11/26(木)@「最近、Go言語始めました」の会
The Go gopher was designed by Renee French.The gopher stickers was made by Takuya Ueda.Licensed under the Creative Commons 3.0 Attributions license.
アジェンダ
● 自己紹介
● Goとは?
● Goroutineの基本
● GoroutineとChannel
● 複数のChannelを扱う
● ファーストクラスオブジェクト
● 単方向のChannel
● for-selectパターン
自己紹介
KLab株式会社KLabGames事業本部 エンジニア
上田拓也twitter: @tenntenn
■ 好きな言語
Go, JavaScript, Lua
■ 業務
モバイルオンラインゲームの開発(クライアントサイド)
Goとは?
Googleが開発しているプログラミング言語
■ 特徴
● 並行プログラミング
● 豊富なライブラリ群
● 強力でシンプルな言語設計と文法
● クロスコンパイル/シングルバイナリ
● go tool
Concurrency is not Parallelism
■ ConcurrencyとParallelismは違う● Concurrency => 並行
● Parallelism => 並列
■ Concurrency● 同時にいくつかの質の異なる事を扱う
■ Parallelism● 同時にいくつかの質の同じ事を行う
by Rob Pike
Concurrency is not Parallelism
■ Concurrency
■ Parallelism
本を運ぶ
本を燃やす
台車を運ぶ
本を積む
本を燃やす 本を燃やす 本を燃やす 本を燃やす
ConcurrencyとGoroutine
■ GoroutineでConcurrencyを実現● 複数のGoroutineで同時に複数のタスクをこなす
● 各Goroutineに役割を与えて分業する
■ 軽量なスレッドのようなもの● LinuxやUnixのスレッドよりコストが低い
● 1つのスレッドの上で複数のGoroutineが動く
■ Goroutineの作り方● goキーワードをつけて関数を呼び出す
go fnc()
複数のコアで動くとは限らない
無名関数とGoroutinepackage mainimport "fmt"import "time"func main() {
go func() {fmt.Println("別のゴールーチン")
}()fmt.Println("mainゴールーチン")time.Sleep(50*time.Millisecond)
}Sleepしないとすぐに終了する
http://play.golang.org/p/jy1HWriRTS
Goroutine-main
Goroutine間のデータのやり取り
Goroutine-2
go f2()
Goroutine-1
go f1()
Goroutine-main
Goroutine間のデータのやり取り
Goroutine-2
go f2()
Goroutine-1
go f1()
変数v
print(v) v = 100
共有の変数を使う?
共有の変数を使う
func main() {done := falsego func() {
time.Sleep(3 * time.Second)done = true
}()for !done {
time.Sleep(time.Millisecond)}fmt.Println("done!")
}
共有の変数を使う
http://play.golang.org/p/mGSOaq4mcr
Goroutine-main
Goroutine間のデータのやり取り
Goroutine-2
go f2()
Goroutine-1
go f1()
変数v
print(v) v = 100
処理順序が保証されない!
競合
共有の変数を使う
n := 1go func() {
for i := 2; i <= 5; i++ {fmt.Println(n, "*", i)n *= itime.Sleep(100)
}}()
http://play.golang.org/p/yqk82u0E4V
for i := 1; i <= 10; i++ {fmt.Println(n, "+", i)n += 1time.Sleep(100)
}
競合
データ競合の解決方法
■ 問題点● どのGoroutineが先にアクセスするか分からない
● 値の変更や参照が競合する
■ 解決方法● 1つの変数には1つのGoroutineからアクセスする
● Channelを使ってGoroutine間で通信をする
● またはロックをとる(syncパッケージ)
"Do not communicate by sharing memory; instead, share memory by communicating"
Goroutine-main
Channelとは?
Goroutine-2
go f2()
Goroutine-1
go f1()
Channel100
ch<-100<-ch
Goroutine間でデータの通信を行うパイプのようなもの
Channelの特徴
■ 送受信できる型● Channelを定義する際に型を指定する
■ バッファ● Channelにバッファを持たせることができる
● 初期化時に指定できる
● 指定しないと容量0となる
■ 送受信時の処理のブロック● 送信時にChannelのバッファが一杯だとブロック
● 受信時にChannel内が空だとブロック
Goroutine-main
送信時のブロック
Goroutine-2
go f2()
Goroutine-1
go f1()
Channel100
ch<-100
受信してくれるまでブロック
ブロック
Goroutine-main
受信時のブロック
Goroutine-2
go f2()
Goroutine-1
go f1()
Channel100
<-ch
送信してくれるまでブロック
ブロック
Channelの基本
■ 初期化
■ 送信
■ 受信
ch1 := make(chan int)ch2 := make(chan int, 10)
ch1 <- 10ch2 <- 10 + 20
n1 := <-ch1n2 := <-ch2 + 100
容量を指定
受け取られるまでブロック
一杯であればブロック
送信されまでブロック
空であればブロック
make(chan int, 0)と同じ
Channelの基本
func main() {done := make(chan bool) // 容量0go func() {
time.Sleep(time.Second * 3)done <- true
}()<-donefmt.Println("done")
}
送信されるまでブロック
http://play.golang.org/p/k0sMCYe4PA
Goroutine-main
複数のChannelから同時に受信
Goroutine-2
go f2()
Goroutine-1
go f1()
ブロックされるので同時に送受信出来ない?
Channel-1 Channel-2
ブロック
Goroutine-main
複数のChannelから同時に受信
Goroutine-2
go f2()
Goroutine-1
go f1()
select-caseを使うと同時に送受信できる
Channel-1 Channel-2
select
select-casefunc main() {
ch1 := make(chan int)ch2 := make(chan string)go func() { ch1<-100 }()go func() { ch2<-"hi" }()
select {case v1 := <-ch1:
fmt.Println(v1)case v2 := <-ch2:
fmt.Println(v2)}
}
http://play.golang.org/p/moVwtEdQIv
先に受信した方を処理
nil Channelfunc main() {
ch1 := make(chan int)var ch2 chan stringgo func() { ch1<-100 }()go func() { ch2<-"hi" }()
select {case v1 := <-ch1:
fmt.Println(v1)case v2 := <-ch2:
fmt.Println(v2)}
}
http://play.golang.org/p/UcqW6WH0XT
nilの場合は無視される
ゼロ値はnil
ファーストクラスオブジェクト
■ ファーストクラスオブジェクト● 変数に入れれる
● 引数に渡す
● 戻り値で返す
● ChannelのChannel
■ timeパッケージ
// 5分間待つ
<-time.After(5 * time.Minute)
http://golang.org/pkg/time/#After
chan chan int など
5分たったら現在時刻が
送られてくるChannelを返す
Channelを引数や戻り値にする
func makeCh() chan int {return make(chan int)
}
func recvCh(recv chan int) int {return <-recv
}
func main() {ch := makeCh()go func() { ch <- 100 }fmt.Println(recvCh(ch))
}
http://play.golang.org/p/vg2RhcdNWR
双方向のChannelfunc makeCh() chan int {
return make(chan int)}
func recvCh(recv chan int) int {go func() { recv <- 200 }()return <-recv
}
func main() {ch := makeCh()go func() { ch <- 100 }()fmt.Println(recvCh(ch))
}
http://play.golang.org/p/6gU92C6Q2v
間違った使い方ができる
単方向のChannelfunc makeCh() chan int {
return make(chan int)}
func recvCh(recv <-chan int) int {return <-recv
}
func main() {ch := makeCh()go func(ch chan<- int) {ch <- 100}(ch)fmt.Println(recvCh(ch))
}
http://play.golang.org/p/pY4u1PU3SU
受信専用のChannel
送信専用のChannel
Concurrencyを実現する
■ 複数Goroutineで分業する● タスクの種類によってGoroutineを作る
● Concurrencyを実現
■ Channelでやりとりする● Goroutine間はChannelで値を共有する
● 複雑すぎる場合はロックを使うことも
■ for-selectパターン● Goroutineごとに無限ループを作る
● メインのGoroutineはselectで結果を受信
Goroutine-main
for-selectパターン
Goroutine-2
go f2()
Goroutine-1
go f1()
各Goroutineで無限ループを作る
Channel-1 Channel-2
select
for{} for{}
つまりこういうこと
Goroutine-1
for{}
Goroutine-2
for{}
Goroutine-3
for{}
Goroutine-4
for{}
Channel
Channel
Channel
Channel
まとめ
■ GoroutineでConcurrencyを実現● go f()で簡単に作れる
■ Channelでやりとりする● 送受信時のブロック
● ファーストクラスオブジェクト
● 単方向のChannel
■ for-selectパターン● Goroutineごとに無限ループを作る
● メインのGoroutineはselectで結果を受信
何か作って発表しよう
■ Go Conferenceに参加しよう
http://eventdots.jp/event/573121
何か作って記事を書こう
■ Go Advent Calendar● http://qiita.com/advent-calendar/2015/go● http://qiita.com/advent-calendar/2015/go2● http://qiita.com/advent-calendar/2015/go3
誰かgo4とgo5を!