Slide 1

Slide 1 text

チャネルのしくみ golang.tokyo #14

Slide 2

Slide 2 text

自己紹介 ● 鎌田健史 ● KLab 株式会社 ○ Unity でエディタ拡張書いたり ○ JS でミニゲーム作ってたり ○ 最近は Python ツールのサポート ● Women Who Go のハンズオンのお手 伝い

Slide 3

Slide 3 text

資料 動画

Slide 4

Slide 4 text

帰ってやってほしいこと ● GopherCon の発表を調べてみる ○ 20 分くらいの発表 + 資料 ● 実際に channel のソースコードを読んで見る

Slide 5

Slide 5 text

話したいこと ● チャネルのおさらい ● チャネルの構造 ○ バッファありチャネルのデータ保存の仕組み ● チャネルとデータの送受信の仕組み ○ シンプルなやり取り ○ 処理をブロックするとき

Slide 6

Slide 6 text

channel のおさらい https://go-tour-jp.appspot.com/concurrency/2 ● goroutine 間で競合を起こさずにデータをやり取りする仕組み ● Queue を持っていてデータを保存できる ● やり取りするデータの型を持っている goroutine1 goroutine2 chan

Slide 7

Slide 7 text

channel のおさらい ● データのやり取りは FIFO ● channel とやり取りできない場合、処理をブロックする ● データをやりとり出来るようになると処理を再開する ● close(chan) することで送信側の終了を伝えることができる ○ range ループで受け取っているときなど

Slide 8

Slide 8 text

コード例 Playgroundで試す

Slide 9

Slide 9 text

channel の構造 ● チャネルには2種類ある ○ バッファあり/なし ● 実装 ○ https://golang.org/src/runtime/chan.go#L31 ● chan T で生成されるものは実は hchan 構造体のポインタ ● メモリ上のヒープ領域に配置される

Slide 10

Slide 10 text

hchan の構造

Slide 11

Slide 11 text

バッファありチャネルの内部 ● buf ○ データを持つための配列への unsafe.Pointer ● lock ○ チャネルとデータのやり取りをするときにロックを取る ● sendx ○ チャネルに送った場合に配列のどこに置かれるか ● recvx ○ チャネルから取り出すときにどの位置から出すか

Slide 12

Slide 12 text

バッファありチャネル ● 生成時に容量を指定するとバッファありチャネル ● ch := make(chan string, 10) buf sendx = 0 lock recvx = 0 ・・・ 0 1 ・・・ 9

Slide 13

Slide 13 text

バッファありチャネル ● 要素を追加すると... buf sendx = 1 lock recvx = 0 0 1 ・・・ 9 ch <- “hello” 0 1 ・・・ 9

Slide 14

Slide 14 text

バッファありチャネル ● 要素を追加すると... buf sendx = 2 lock recvx = 0 0 1 ・・・ 9 0 1 ・・・ 9

Slide 15

Slide 15 text

バッファありチャネル ● 一杯になると... buf sendx = 0 lock recvx = 0 0 1 ・・・ 9 0 1 ・・・ 9

Slide 16

Slide 16 text

バッファありチャネル ● 要素を取りだすと... buf sendx = 0 lock recvx = 1 0 1 ・・・ 9 0 1 ・・・ 9

Slide 17

Slide 17 text

まとめ ● chan T の実態は hchan のポインタ ● バッファありチャネルのバッファは FIFO ● その実装はデータを取り出すためのインデックスとデータを受け取 るためのインデックスで実装されている

Slide 18

Slide 18 text

チャネルの送受信

Slide 19

Slide 19 text

シンプルな送受信 1. ロックを取る 2. バッファにコピー/バッファからコピー 3. ロック解除

Slide 20

Slide 20 text

送信 buf sendx = 0 lock recvx = 0 0 1 ・・・ 9 0 1 ・・・ 9

Slide 21

Slide 21 text

送信 buf sendx = 0 lock recvx = 0 0 1 ・・・ 9 0 1 ・・・ 9

Slide 22

Slide 22 text

送信 buf sendx = 0 lock recvx = 0 0 1 ・・・ 9 0 1 ・・・ 9

Slide 23

Slide 23 text

送信 buf sendx = 1 lock recvx = 0 0 1 ・・・ 9 0 1 ・・・ 9

Slide 24

Slide 24 text

送信 buf sendx = 1 lock recvx = 0 0 1 ・・・ 9 0 1 ・・・ 9

Slide 25

Slide 25 text

受信 buf sendx = 1 lock recvx = 0 0 1 ・・・ 9

Slide 26

Slide 26 text

受信 buf sendx = 1 lock recvx = 0 0 1 ・・・ 9

Slide 27

Slide 27 text

受信 buf sendx = 1 lock recvx = 0 0 1 ・・・ 9

Slide 28

Slide 28 text

受信 buf sendx = 1 lock recvx = 1 0 1 ・・・ 9

Slide 29

Slide 29 text

バッファを超える送信をした場合 buf sendx = 0 lock recvx = 0 0 1 ・・・ 9

Slide 30

Slide 30 text

バッファを超える送信をした場合 buf sendx = 0 lock recvx = 0 0 1 ・・・ 9 送信をブロックしなければいけない

Slide 31

Slide 31 text

寄り道: goroutine のスケジューリングの話 ● M:N thread モデル ○ M 個の OS thread で N 個のgoroutine を分担して動かす ● それぞれの OS thread に goroutine の Queue を持つ ● キリが良いところまで実行して次の goroutine を実行する

Slide 32

Slide 32 text

G M(OS thread) G G(goroutine) P(Context of Scheduling)

Slide 33

Slide 33 text

M 個の OS thread G M(OS thread) G G(goroutine) P(Context of Scheduling) G M(OS thread) G G(goroutine) P(Context of Scheduling) ・・・

Slide 34

Slide 34 text

参考資料 ● Goならわかるシステムプログラミング - Go言語と並列処理(2) ● Go のスケジューラ実装とハマりポイント

Slide 35

Slide 35 text

送信をブロックする 1. スケジューラに対して止めるよう指示する(gopark) 2. OS thread は対象の goroutine を止めて実行から外す 3. 次の goroutine を実行する

Slide 36

Slide 36 text

G M(OS thread) G G(goroutine) P(Context of Scheduling)

Slide 37

Slide 37 text

G G M(OS thread) G G(goroutine) P(Context of Scheduling) ch <- "schlusco"

Slide 38

Slide 38 text

channel から見たブロック ● goroutine を停止する命令を投げる ● 今の goroutine と送信用の値を Queue に保存

Slide 39

Slide 39 text

buf lock sendq recvq sudog g elem Function01 schlusco

Slide 40

Slide 40 text

goroutine の再開 ● シンプルに取りだす処理を行う ● sudog にある要素を取りだして buf に追加 ● sudog にある goroutine を実行待ち状態にする

Slide 41

Slide 41 text

buf lock sendq recvq sudog g elem Function01 schlusco 0 1 ・・・ 9

Slide 42

Slide 42 text

buf lock sendq recvq sudog g elem Function01 schlusco 0 1 ・・・ 9

Slide 43

Slide 43 text

buf lock sendq recvq sudog g elem Function01 schlusco 0 1 ・・・ 9

Slide 44

Slide 44 text

buf lock sendq recvq sudog g elem Function01 schlusco 0 1 ・・・ 9

Slide 45

Slide 45 text

G G M(OS thread) G G(goroutine) P(Context of Scheduling)

Slide 46

Slide 46 text

受信側がブロックする場合 ● 基本的に送信側がブロックする場合と逆

Slide 47

Slide 47 text

G M(OS thread) G G(goroutine) P(Context of Scheduling)

Slide 48

Slide 48 text

G G M(OS thread) G G(goroutine) P(Context of Scheduling) food := <-ch

Slide 49

Slide 49 text

buf lock sendq recvq sudog g elem Function02 food

Slide 50

Slide 50 text

goroutine を再開 ● 送信処理を行う ● sudog にある要素に buf の内容を渡す ● sudog にある goroutine を実行待ち状態にする

Slide 51

Slide 51 text

buf lock sendq recvq sudog g elem Function02 food 0 1 ・・・ 9

Slide 52

Slide 52 text

buf lock sendq recvq sudog g elem Function02 food 0 1 ・・・ 9

Slide 53

Slide 53 text

buf lock sendq recvq sudog g elem Function02 food := “sushi” 0 1 ・・・ 9

Slide 54

Slide 54 text

buf lock sendq recvq sudog g elem Function02 food := “sushi” 0 1 ・・・ 9

Slide 55

Slide 55 text

もっと賢く ● sudog の elem は受取先のポインタ ● なのでここに直接書いてしまえばOK! ● メモリコピーと lock を取る回数も減って嬉しい

Slide 56

Slide 56 text

buf lock sendq recvq sudog g elem Function02 food 0 1 ・・・ 9

Slide 57

Slide 57 text

buf lock sendq recvq sudog g elem Function02 food := “sushi” 0 1 ・・・ 9

Slide 58

Slide 58 text

バッファなしチャネルの話 ● 基本的にこれまで話してきたことと同じ ● バッファがないので常にブロックが発生する

Slide 59

Slide 59 text

Select で使う場合 ● 全部の channel をロックする ● 全部の channel の sudog に自分を入れて待ち状態にする ● 勝った case のところから処理を再開する

Slide 60

Slide 60 text

まとめ ● channel は使い方はシンプル ● だけど裏ではしっかり並列処理のための仕組みが動いている ● 発表資料はとても勉強になった ○ Gopher Con は行けないけど他の資料も見ていきたい