O’REILLYの「Go言語による並行処理」を読んので、自分用にメモを残しておく。 また、折角なのでhttps://godoc.org/github.com/bobuhiro11/gostream に、本文のアイデアをもとに並行処理に便利な関数群をまとめた。

  • 競合状態は二つ以上の操作を正しい順番で実行しなければいけない状況で、プログラムがその順番を保証していないときに発生する。
    • 論的な正当性を目指すべきで、Sleep関数を挟むような方法では解決しない。
  • デッドロックが起きる必要条件は、Coffman条件として知られている。
  • 筆者の意見では、ライブロックはデッドロックよりも発見がむずかしい。プログラムの外部からリソース使用率を観測しても検出できないから。
  • 並行処理に関わる関数には、適切にコメントをつける。誰が並行処理を担っているか、どのような並行処理のプリミティブに対応しているか、誰が同期処理を担っているか、を記述する。関数シグネチャでは不十分。
  • 並行性(concurrent)はコードの性質、並列性(parallel)は実行中のプログラムの性質を指す。
  • Tony hoare「Communicationg Sequence Process」をもとに、Go言語の並行処理プリミティブは設計された。
    • CSPのほかにも、伝統的なメモリアクセス同期のコードも書ける。syncパッケージ中にまとめられている。
    • ただ、Goプログラミングスタイルでは、高水準の技術を使うのが推奨されている。ある瞬間に、ただ一つのゴルーチンのみが特定のデータを扱うようにすべき。
    • メモリ共有のかわりに、チャネルによる通信を行うべき。
  • メモリサイズに応じて、生成可能なゴルーチン数を概算できる。本文中の例によると、RAM 8GBで数百万のゴルーチンを生成できる。
  • sync.WaitGroupAddの呼び出しはできる限り監視対象のゴルーチンの直前に書くのが良い。
  • デッドロックを避けるために、Mutexを使う時はUnlockの呼び出しをdeferの中で行う。
  • RWMutexMutexよりも高機能。書き込みのロックを取っているものがいなければ、複数の読み込みロックを取得できる。
  • バッファ付きチャネルは早すぎる最適化になりやすい。デッドロックが見えなくなってしまう。
  • チャネルの所有権のスコープは小さくする。
  • Goランタイムはcase文全体に対して疑似乱数による一様選択をしている。
  • selectselect {}は永遠にブロックする。
  • 情報をたった一つの並行プロセスからのみ得られることを確実にする考え方に、拘束がある。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// チャネルへの書き込みスコープは、関数内にレキシカルに拘束される。
func owner() <-chan int {
  c := make(chan int)
  go runc() {
    defer close(c)
    for i:=0; i<10; i++ {
      c <- i
    }
  }
  return c
}
  • システムにキューを導入するのは便利だが、早すぎる最適化になってしまう。キュー導入の利点は、ステージの実行時間が減ることはなく、ステージのブロック状態の時間が減ること。
  • キューでは、リトルの法則でスループットを予測できる。
  • contextを関数の第一引数として渡す。よく使われるデータへの参照の保管には使わない。
  • ハートビートは必ずしも必要ではない。長時間稼働させる場合には有用。