PREMIUM

Go言語入門 並行処理編

Go言語における並行処理の使い方について見ていきます。
0%

このレッスンをはじめる前に

このレッスンを進めるには前提知識が必要です。先に以下のレッスンを完了しておきましょう。

  • #01 並行処理を使ってみよう (02:15) 無料公開中
    • 今回扱うトピック、並行処理
    • 通常、プログラムは処理を書いた順に実行される
    • 並行処理: CPUでの処理を切り替えながら少しずつ実行して同時進行しているかのように処理すること
    • 使い所が重要: 処理に待ち時間が発生するときに効果を発揮する
    • CPUをずっと使う処理は処理時間は変わらない、非効率になる場合もある。処理Aの結果を処理Bで使うときは分割自体が不可
    • goroutineという処理に変換すると、簡単に並行処理を実装できる
    • 並行処理を学ぶための準備、処理に待ち時間がある関数を作成
    • 便利な命令が詰まったパッケージ: fmt、time
    • 複数のパッケージを()でまとめる
    • 1秒待つ: time.Sleep(time.Second * 1)
    • 次回、並行処理を使って高速化する
  • #03 goroutineを使ってみよう (01:57) 無料公開中
    • goroutineに変換、go doSomething()
    • go doSomething()終了前にmain()終了、メッセージが表示されない → time.Sleep(time.Second * 3)で修正
    • goキーワード: すべての関数に付与可能、引数可、ただし返り値は無視される
    • goキーワードで返り値が必要な際、チャネルという仕組みを使う(次回)
    • チャネルで値を送る、受け取る準備
    • チャネル: goroutineの間で値の受け渡しにつかうパイプのようなもの
    • チャネル基本: var ch chan int = make(chan int)
    • チャネル書き換え: ch := make(chan int)
    • 引数を設定: go double(10, ch)
    • チャネルに値を送る準備: ch <- num * 2(<- チャネル演算子)
    • チャネルから値を受け取り、確認
    • チャネルの値を受け取る: result := <-ch
    • チャネル演算子注意点: ch <- num * 2とresult := <-ch、<-まわりの空白の違い
    • チャネルはスライスやマップと同じくポインタ
    • チャネルを使うと、3秒待たず、値がきちんと表示されるのはなぜか?
    • 図を使って説明
    • チャネルを使うと、受信側と送信側の準備が整うまで値の受け渡しを待ってくれる
    • 以下、チャネルを使ったときの重要な挙動
    • 値の受け渡しができる
    • 並行処理を行いつつ、処理Bが終わるのをまって、処理Aを再開することができる
    • go double()を複数起動、すべての返り値を受け取るまで待ち、最後に合計を表示
    • go double(10, ch)、go double(20, ch)、go double(30, ch)
    • fmt.Println(<-ch + <-ch + <-ch)
    • 実装例: 複数の銀行サイトの問い合わせ、残高取得、最後に合計表示
    • goroutineは同時に実行されるため、順番に結果がかえってくるわけではない
    • 順番を制御したい場合、チャネルを複数作成で実現
    • go double(10, ch1)、go double(20, ch2)、go double(30, ch3)
    • fmt.Println(<-ch1)の結果が返ってくるまで、次の行は実行されない
    • selectでは、最初にデータを受け取った値を使うことができる
    • selectを使うための準備
    • お天気情報取得のため、複数サイトに問い合わせをするコードを実装
    • selectを実装、switchと記法が似ている
    • 各caseで変数を宣言しているため、同じ変数名を使い回すことができる、weather
    • 実装したコードでは、最初に値を受け取ったチャネルに対応する処理だけが実行される
    • 同時に値を受け取った場合はランダムに選ばれる
    • 前回の続き、値を受け取れなかった場合の処理方法
    • 一定期間を過ぎたらタイムアウトする処理を追加
    • time.After(time.Second * 2): 2秒後、何らかの値が送信されたチャネルを返す
    • 2秒以内に値を受け取らなければrequest timeoutが表示される
    • case <-time.After()では受け取った値を利用しないため、変数には代入しない
    • チャネルで値の受け渡しをしない場合のgoroutine処理順を制限するための準備
    • 処理時間の計測: start := time.Now()、time.Since(start)
    • 次回、処理の正確な実行時間を把握し、チャネルを使わずに処理順を制御する
    • WaitGroupという仕組みで実現
    • チャネルを使わずに処理時間を計測、処理順を制限する
    • syncパッケージインポート
    • wgはポインタではない、&wg、*sync.WaitGroup、デリファレンス不要
    • var wg sync.WaitGroup
    • wg.Add(): いくつのgoroutineを待ちたいかを指定
    • wg.Wait(): 処理を待ちたいところに追加
    • wg.Done(): 処理が終わるたびに実行
    • WaitGroupを使えばgoroutineの処理が終わるまで待つことができる
    • 並行処理でおこる問題を知る
    • レースコンディション
    • レースコンディションを図で説明
    • 複数の処理が同時進行した場合、数値を同時に読み込んでしまい、数値がおかしくなる
    • Mutex: 値が同時に読み込まれることがないようにする仕組み
    • 1つの処理が値にアクセスしたら、値にロックをかけて、他の処理が呼び出せないようにする
    • レースコンディションを解決する
    • syncパッケージインポート
    • var mu sync.Mutex
    • muはポインタではない、&mu、mu *sync.Mutex
    • mu.Lock(): 他のgoroutineがそれ以降の処理にアクセスできないようにする
    • mu.Unlock(): ロックを解除
    • waitGroup同様、デリファレンス不要