チャネルの仕組み

 チャネルの仕組み

2018/04/16 golang.tokyo #14 の発表です

Fd1ded499f7831ddb01b0d23eb9b64c2?s=128

Kenshi Kamata

April 16, 2018
Tweet

Transcript

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

  2. 自己紹介 • 鎌田健史 • KLab 株式会社 ◦ Unity でエディタ拡張書いたり ◦

    JS でミニゲーム作ってたり ◦ 最近は Python ツールのサポート • Women Who Go のハンズオンのお手 伝い
  3. 資料 動画

  4. 帰ってやってほしいこと • GopherCon の発表を調べてみる ◦ 20 分くらいの発表 + 資料 •

    実際に channel のソースコードを読んで見る
  5. 話したいこと • チャネルのおさらい • チャネルの構造 ◦ バッファありチャネルのデータ保存の仕組み • チャネルとデータの送受信の仕組み ◦

    シンプルなやり取り ◦ 処理をブロックするとき
  6. channel のおさらい https://go-tour-jp.appspot.com/concurrency/2 • goroutine 間で競合を起こさずにデータをやり取りする仕組み • Queue を持っていてデータを保存できる •

    やり取りするデータの型を持っている goroutine1 goroutine2 chan
  7. channel のおさらい • データのやり取りは FIFO • channel とやり取りできない場合、処理をブロックする • データをやりとり出来るようになると処理を再開する

    • close(chan) することで送信側の終了を伝えることができる ◦ range ループで受け取っているときなど
  8. コード例 Playgroundで試す

  9. channel の構造 • チャネルには2種類ある ◦ バッファあり/なし • 実装 ◦ https://golang.org/src/runtime/chan.go#L31

    • chan T で生成されるものは実は hchan 構造体のポインタ • メモリ上のヒープ領域に配置される
  10. hchan の構造

  11. バッファありチャネルの内部 • buf ◦ データを持つための配列への unsafe.Pointer • lock ◦ チャネルとデータのやり取りをするときにロックを取る

    • sendx ◦ チャネルに送った場合に配列のどこに置かれるか • recvx ◦ チャネルから取り出すときにどの位置から出すか
  12. バッファありチャネル • 生成時に容量を指定するとバッファありチャネル • ch := make(chan string, 10) buf

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

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

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

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

    1 0 1 ・・・ 9 0 1 ・・・ 9
  17. まとめ • chan T の実態は hchan のポインタ • バッファありチャネルのバッファは FIFO

    • その実装はデータを取り出すためのインデックスとデータを受け取 るためのインデックスで実装されている
  18. チャネルの送受信

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

  20. 送信 buf sendx = 0 lock recvx = 0 0

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

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

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

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

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

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

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

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

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

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

    1 ・・・ 9 送信をブロックしなければいけない
  31. 寄り道: goroutine のスケジューリングの話 • M:N thread モデル ◦ M 個の

    OS thread で N 個のgoroutine を分担して動かす • それぞれの OS thread に goroutine の Queue を持つ • キリが良いところまで実行して次の goroutine を実行する
  32. G M(OS thread) G G(goroutine) P(Context of Scheduling)

  33. 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) ・・・
  34. 参考資料 • Goならわかるシステムプログラミング - Go言語と並列処理(2) • Go のスケジューラ実装とハマりポイント

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

    次の goroutine を実行する
  36. G M(OS thread) G G(goroutine) P(Context of Scheduling)

  37. G G M(OS thread) G G(goroutine) P(Context of Scheduling) ch

    <- "schlusco"
  38. channel から見たブロック • goroutine を停止する命令を投げる • 今の goroutine と送信用の値を Queue

    に保存
  39. buf lock sendq recvq sudog g elem Function01 schlusco

  40. goroutine の再開 • シンプルに取りだす処理を行う • sudog にある要素を取りだして buf に追加 •

    sudog にある goroutine を実行待ち状態にする
  41. buf lock sendq recvq sudog g elem Function01 schlusco 0

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

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

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

    1 ・・・ 9
  45. G G M(OS thread) G G(goroutine) P(Context of Scheduling)

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

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

  48. G G M(OS thread) G G(goroutine) P(Context of Scheduling) food

    := <-ch
  49. buf lock sendq recvq sudog g elem Function02 food

  50. goroutine を再開 • 送信処理を行う • sudog にある要素に buf の内容を渡す •

    sudog にある goroutine を実行待ち状態にする
  51. buf lock sendq recvq sudog g elem Function02 food 0

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

    1 ・・・ 9
  53. buf lock sendq recvq sudog g elem Function02 food :=

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

    “sushi” 0 1 ・・・ 9
  55. もっと賢く • sudog の elem は受取先のポインタ • なのでここに直接書いてしまえばOK! • メモリコピーと

    lock を取る回数も減って嬉しい
  56. buf lock sendq recvq sudog g elem Function02 food 0

    1 ・・・ 9
  57. buf lock sendq recvq sudog g elem Function02 food :=

    “sushi” 0 1 ・・・ 9
  58. バッファなしチャネルの話 • 基本的にこれまで話してきたことと同じ • バッファがないので常にブロックが発生する

  59. Select で使う場合 • 全部の channel をロックする • 全部の channel の

    sudog に自分を入れて待ち状態にする • 勝った case のところから処理を再開する
  60. まとめ • channel は使い方はシンプル • だけど裏ではしっかり並列処理のための仕組みが動いている • 発表資料はとても勉強になった ◦ Gopher

    Con は行けないけど他の資料も見ていきたい