Upgrade to Pro — share decks privately, control downloads, hide ads and more …

並行処理入門 -Goで遊ぶ-

並行処理入門 -Goで遊ぶ-

stonriver

March 16, 2019
Tweet

More Decks by stonriver

Other Decks in Technology

Transcript

  1. 関数 Goは通常の関数以外に無名 関数を利用することができ ます。無名関数は一般の関 数と同等に扱えます。 出力はどうなるでしょうか 19 // hello関数宣言 func

    hello() { fmt.Println("Hello.") } // main関数宣言(エントリポイント) func main() { // 無名関数を変数に bye := func() { fmt.Println("Bye.") } // 呼び出し hello() bye() // 無名関数呼び出し func() { fmt.Println("Hai.") }() }
  2. ゴルーチン(簡易説明) ゴルーチンは、Goのスレッ ドのようなものです。(後述) なんと関数呼び出しに"go"を つけるだけで、独立して実行 されます。 検証のため、sleepを入れて います。 21 func

    hello() { time.Sleep(time.Second * 2) // 2秒 sleep fmt.Println("Hello.") } func main() { bye := func() { time.Sleep(time.Second) // 1秒 sleep fmt.Println("Bye.") } // ゴルーチンとして関数呼び出し go hello() go bye() go func() { time.Sleep(time.Second * 3) // 3秒 sleep fmt.Println("Hey.") }() time.Sleep(time.Second * 4) // 4秒 sleep } 出力はどうなるでしょうか
  3. 実行タイミング ここに、並行処理を用いた サンプルコードを用意しま した。 先程紹介したGoの機能の範 疇ですが、ある問題が存在 します。 26 func main()

    { // ゴルーチンとして関数を起動 go func() { fmt.Println("CALL GOROUTINE!!") }() fmt.Println("MAIN GOROUTINE!!") } 出力はどうなるでしょうか
  4. 実行タイミング これは、並行処理が起動す るまでの時間が、直後の main関数の終了よりも遅 かったことで、標準出力が 行われるよりも前にプロセ ス自体が終了してしまった ことが原因です。 sleepを入れてみます。 28

    func main() { // ゴルーチンとして関数を起動 go func() { fmt.Println("CALL GOROUTINE!!") }() fmt.Println("MAIN GOROUTINE!!") time.Sleep(time.Second) // 1秒 sleep } 出力はどうなるでしょうか
  5. クリティカルセクション このコードにはアトミック 性が無く、クリティカルセ クションが存在します。 実行してみましょう。 40 func main() { var

    wg sync.WaitGroup val := 0 // インクリメント無名関数 inc := func() { fmt.Println(val) val++ } const routinenum = 20 wg.Add(routinenum) // ゴルーチン起動数を登録 for i := 0; i < routinenum; i++ { // ゴルーチン呼び出し go func() { // ゴルーチン終了時に終了報告 defer wg.Done() inc() }() } wg.Wait() // 登録ゴルーチンの終了待ち fmt.Println("Finish.") } 出力はどうなるでしょうか
  6. クリティカルセクション このコードのクリティカルセ クションはここです。 ゴルーチンとして呼び出され る無名関数の中で同じスコー プの変数にアクセスが行われ ています。 メモリアクセス競合の可能性 があり、アトミック性があり ません。

    42 func main() { var wg sync.WaitGroup val := 0 inc := func() { // クリティカルセクション fmt.Println(val)                val++                      } const routinenum = 20 wg.Add(routinenum) // ゴルーチン起動数を登録 for i := 0; i < routinenum; i++ { go func() { defer wg.Done() inc() }() } wg.Wait() // 登録ゴルーチンの終了待ち fmt.Println("Finish.") }
  7. クリティカルセクション ここで、クリティカルセク ションの間に他のゴルーチン のメモリアクセスをロックす ることでアトミック性を持た せてみます。 43 func main() {

    var lock sync.Mutex var wg sync.WaitGroup val := 0 inc := func() { // スコープに存在する変数への他アクセスをロック lock.Lock()              defer lock.Unlock()              fmt.Println(val) val++ } const routinenum = 20 wg.Add(routinenum) for i := 0; i < routinenum; i++ { go func() { defer wg.Done() inc() }() } wg.Wait() fmt.Println("Finish.") }
  8. デッドロック sumは、引数を2つ取って、 アトミック性を確保するため に受け取った第1引数を排他 的権利でロックします。 特定の引数で並行して起動す ると、お互いのロックする変 数を待ち合います。 これは外部から止めることが できません。

    48 type value struct { mu sync.Mutex value int } func main() { var wg sync.WaitGroup sum := func(v1, v2 *value) { defer wg.Done() v1.mu.Lock() defer v1.mu.Unlock() time.Sleep(time.Second * 2) // 戦犯 v2.mu.Lock() defer v2.mu.Unlock() fmt.Printf("%d+%d=%d\n", v1.value, v2.value, v1.value+v2.value) } a := value{value: 10} // 値初期化 b := value{value: 20} wg.Add(2) go sum(&a, &b) // sum起動その1 go sum(&b, &a) // sum起動その2 wg.Wait() }
  9. ゴルーチン スレッドと類似のGo独自の 概念で、Goの最も基本的な 構成単位。 自動で環境に合わせてスケー ルし、並行や並列で動作す る。 "go"をつけるだけで起動可能 で非常に高機能。 61

    func hello() { time.Sleep(time.Second * 2) // 2秒 sleep fmt.Println("Hello.") } func main() { bye := func() { time.Sleep(time.Second) // 1秒 sleep fmt.Println("Bye.") } // ゴルーチンとして関数呼び出し go hello() go bye() go func() { time.Sleep(time.Second * 3) // 3秒 sleep fmt.Println("Hey.") }() time.Sleep(time.Second * 4) // 4秒 sleep }
  10. チャネル チャネルは、主に並行プロセ スが通信に利用するための キューです。 makeの引数で格納可能数を指 定できます。 "<-"をチャネルの左右に置く ことで、チャネル内部の キューと値を送受信します。 65

    func f(ch chan int) { defer close(ch) ch <- 10 ch <- 15 } func main() { ch := make(chan int, 1) go f(ch) fmt.Println(<-ch) fmt.Println(<-ch) // ゴルーチン起動前に終了してしまわないか? } 出力はどうなるでしょうか
  11. チャネル 先程デッドロックを起こして いたコードをチャネルを使っ て実装してみました。 67 func main() { var wg

    sync.WaitGroup sum := func(ch chan int) { defer wg.Done() time.Sleep(time.Second * 2) v1 := <-ch v2 := <-ch fmt.Printf("%d+%d=%d\n", v1, v2, v1+v2) } ch := make(chan int, 2) wg.Add(2) a, b := 10, 20 go sum(ch) go sum(ch) ch <- b ch <- a ch <- a ch <- b wg.Wait() } 出力はどうなるでしょうか
  12. チャネル 通信に向けた機能を持つ キューであるため、普通に FIFOのキューとして利用する ことも可能。 69 func main() { ch

    := make(chan int, 10) defer close(ch) ch <- 1 ch <- 2 ch <- 3 ch <- 4 fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch) } 出力はどうなるでしょうか
  13. select文 多数のチャネルの受信を管理 し、チャネルごとに処理を場 合分けできる。 switch文に似ているが、順序 は無くすべての場合が同時に 判定されている。 ある程度の規模になると必須 71 func

    main() { ch1 := make(chan int) ch2 := make(chan string) chend := make(chan struct{}) go func(chint chan<- int, chstr chan<- string, chend chan<- struct{}) { for i := 1; i < 16; i++ { if i % 5 + i % 3 == 0 { chstr <- "FizzBuzz" } else if i % 3 == 0 { chstr <- "Fizz" } else if i % 5 == 0 { chstr <- "Buzz" } else { chint <- i } } close(chend) }(ch1, ch2, chend) for { select { case val := <-ch1: fmt.Println(val) case str := <-ch2: fmt.Println(str) case <-chend: fmt.Println("finish.") return } } }