Skip to content

Instantly share code, notes, and snippets.

@evalphobia evalphobia/08.md
Last active Mar 23, 2017

Embed
What would you like to do?
The Go Programming Language

8 ゴルーチンとチャネル

2つのスタイルの並行処理

  • CSPモデル
    • プロセス間で直接相互通信をしない (?)
    • Goでは独立したプロセスとしてゴルーチンを使い、相互通信のためにチャネルを用いる(?)
  • 伝統的な共有メモリマルチスレッディング
    • 他の言語では一般的にスレッドが使われる
    • Ch.9で取り扱う

8.1 ゴルーチン

Goでは並行に実行にしている動作を goroutine と呼ぶ。

- 通常のプログラムでは1つの関数が終わってから次の関数を実行することができる
- 並行なプログラムでは同時に2つ以上の関数を実行することが出来る
- 他の言語のスレッドのようなもの
    - スレッドとは質的な違いよりも量的な違いが大きい
    - goroutineは大量に実行しても、スレッドよりメモリを食わず軽いため大丈夫 (Ch.9)
  • プログラム実行開始時には main関数 が唯一のゴルーチンでメインゴルーチンと呼ぶ。
    • 新しいゴルーチンは go 文によって作られる。
    • 関数・メソッドの前に go キーワードを付けるだけ
f()     // f()を呼び出し関数の終了を待つ
go f()  // 生成された新たなゴルーチンがf()を呼び出す. 関数の終了を待たない
  • [45番目のフィボナッチ数を計算し、スピナーを表示する]

  • main関数の実行が終了すると全てのゴルーチンが停止しプログラムが終了する

    • あるゴルーチンから他のゴルーチンを直接停止させることはできない
    • 他のゴルーチンと通信をし、そのゴルーチン自身で停止するようにすることはできる

8.2 例: 並行時計サーバー

  • https://github.com/adonovan/gopl.io/blob/master/ch8/clock1/clock.go

    • 毎秒 現在時刻をクライアントに対して返却するサーバー
    • net.Listen
      • TCP localhost:8000 をリッスンしている net.Listener を返却する
      • Acceptメソッドはリクエストを受信するまでブロックする
      • リクエストを受信すると、コネクションを表す net.Conn を返却する
    • handleConn
      • クライアントから1リクエストの最初から最後までを扱う
      • time.Now() で現在時刻を書き出す
      • net.Conn構造体は io.Writerインターフェースを満たすため、io.WriteStringで直接書き出すことができる
      • 書き込みに失敗すると、ループを終了しCloseでそのコネクションを閉じる
    • time.Time.Formatメソッド
      • 任意の書式で時刻を出力することができる
      • ただし決まった時刻のテンプレートがあるので注意(time.Parseも同様)
  • https://github.com/adonovan/gopl.io/blob/master/ch8/netcat1/netcat.go

    • 読み込み専用のTCPクライアント
    • コネクションからデータを読み込み、EOFまたはエラーが発生するまで標準出力に書き込み続ける
  • 最初のclockサーバーに対して、↑のnetcatクライアントを2つ同時に起動しても、同時に1つしか時刻が出力されない。

    • サーバーがシーケンシャルなので、一つのクライアントしか処理できない
    • handleConn に go キーワードを付けて、独立したゴルーチンで動作するようにし、並行処理が行えるようにする。
  • https://github.com/adonovan/gopl.io/blob/master/ch8/clock2/clock.go

    • clock1handlerConngo 付けただけ

8.3 例: 並行エコーサーバー

8.2の時計サーバーでは1リクエストにつき1ゴルーチンを使用した。 このエコーサーバーでは1リクエストにつき複数のゴルーチンを使うようにする。

簡単なエコーサーバーは以下のような形で作ることが出来る

func handleConn(c net.Conn) {
	io.Copy(c, c)
	c.Close()
}

このプログラムでは、エコー出力3回が終わらなくても新たな入力を受け付けることができるが、 最初のエコー出力が終わるまで次のエコー出力は始まらない。 これを直すには go キーワードを echo 部分にさらに追加する必要がある。

go というキーワードをつけるだけで並行処理が出来たが、 並行に実行して大丈夫かどうか確認しなくてはならない。 今回の net.Conn では並行にメソッドを実行して平気だったが、多くの型では並行にメソッドを実行することは危険。 次の章では並行性の安全性について重要なコンセプトについて見ていく。

8.4 チャネル

  • ゴルーチン ≒ 並行なGoプログラムの動作
  • チャネル ≒ ゴルーチン間のコネクション
    • あるゴルーチンから他のゴルーチンへ値を送信するための通信手段
    • 全てのチャネルが、特定の型のための値の通信手段となる
      • 型は チャネルの要素型 という呼び方をする
    • チャネルは参照型(mapやsliceと同様)
      • nilや同一値との比較が可能

チャネルを作るには、組み込み関数の make を使う

ch := make(chan int)  // ch は chan int型

チャネルの操作には送信と受信があり、両方とも <- 演算子を使用する。

ch <- x   // 送信文

x = <-ch  // 代入文内の受信式
<-ch      // 受信式. 結果は破棄される

組み込み関数の close でチャネルを閉じることができる。

close(ch)

閉じたチャネルに送信するとパニックが発生するが、閉じたチャネルからの受信は値がなくなるまで可能。

単にmakeだけで作られたチャネルは「バッファなしチャネル」となる。 makeにチャネルの容量となる第二引数を付けて、1以上の容量を持つ場合は「バッファありチャネル」となる。

8.4.1 バッファなしチャネル

  • 送信・受信ともに先に行われた方がブロックされる

    • 送信の場合は受信を、受信の場合は送信を待ち続ける
    • 送信ゴルーチンと受信ゴルーチン間で同期することが可能
    • 送信ゴルーチンからチャネルへ値が送られた後、送信ゴルーチンのブロック解除よりも先に値の受信が行われる(ことが保証される?)
  • https://github.com/adonovan/gopl.io/blob/master/ch8/netcat3/netcat.go

    • コネクション断を検知しエラーハンドリングしたバージョン
    • 値よりも通信の瞬間や発生が重要な場合はメッセージをイベントと呼ぶことがある
    • チャネルの要素型に struct{} 型を使い、目的が同期のみであることを強調

8.4.2 パイプライン

チャネルをゴルーチン間を接続し、あるゴルーチンの出力が他のゴルーチンの入力となるようにすることができる。 これをパイプラインと呼ぶ。

3つのゴルーチンを2つのチャネルで接続するイメージ

[Counter] -> [Squater] -> [Printer]

- Counter: 数値を生成する
- Squater: Counterから受け取った数値を二乗する
- Printer: Squaterから受け取った数値を出力する

https://github.com/adonovan/gopl.io/blob/master/ch8/pipeline1/main.go - パイプラインの簡単な例

8.4.3 単方向チャネル型

8.4.4 バッファありチャネル

8.5 並列ループ

8.6 例: 並行Webクローラー

8.7 selectによるマルチプレクサ

8.8 例: 並行ディレクトリ探索

8.9 キャンセル

8.10 例: チャットサーバー

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.