Yabu.log

ITなどの雑記

第22回横浜Go読書会に参加

go言語に関する書籍を読む会です。今回から本が新しくなり『Go言語による並行処理』を読んで行きます。

Go言語による並行処理

Go言語による並行処理

初回なので参加者の方が多かったです(初参加の方も)

yokohama-go-reading.connpass.com

誤植など

epub版固有の誤植というのがいくつか見つかりました。

github.com

github.com

ちなみに電子版で呼んでる方はpdfが多い気がしますが、私はepub派です。

組み版や翻訳、原著との差分に関して気になる点

柴田さんをはじめ、複数の方から意見が出ました。

  • 「驚異的並列」の元の単語(embarrassingly parallel)を書いて欲しい。

    • 対応する原文の単語をよく併記している(by柴田さん)
  • 原著ではソースに行番号が書かれている。

    • p5では3行目、5行目と本文中で参照されているので消すべきではないと思うが...
    var data int
    go func() { // ❶
        data++
    }()
    if data == 0 {
        fmt.Printf("the value is %v.\n", data)
    }

ここで、3 行目と 5 行目ではともに dataという変数にアクセスしようとしています。

  • 組み版が気になる。原著ではソースがまたがっている(p5-6)が、これは無理やり改ページした後にソースを掲載するべき

並列性と並行性

  • Goでは並列処理がかけない。並行処理だけ。並列処理ができる言語はないような気がする。
  • 原著では並行性は Concurrency ,並列性は Parallelism になっているはず

    • 確認したところそのようになってた。
  • アムダールの法則:並列化による性能向上

  • Spigotアルゴリズム:円周率の計算を並列化する方法

  • CPUのクロックは長いこと3,4Ghzで止まっている。2010年頃は10GHzの時代が来る!とか言ってた時代もあった

    • 2000年前後だそうです
  • 競合状態(race condition)

    • 競合状態の検出は困難。
  • 並列性を実現するのはシステムコールでやる。

  • 過去にはシングルコアで動かないプログラムをマルチコアCPUで動かすと、想定していなかった競合に関するバグが発生した。

    • 対策としてCPUのコアをbiosで無効化していた。
      • 無効化するのはもったいので、CPUコアに特定のプロセスを割り当てるシステムコールがOSに導入された

golangデッドロック検出機能

デッドロック検出のプログラミング言語を動かした例。

type value struct {
  mu    sync.Mutex
  value int
}

var wg sync.WaitGroup
printSum := func(v1, v2 *value) {
  defer wg.Done()
  v1.mu.Lock()         // ❶
  defer v1.mu.Unlock() // ❷

  time.Sleep(2 * time.Second) // ❸
  v2.mu.Lock()
  defer v2.mu.Unlock()

  fmt.Printf("sum=%v\n", v1.value+v2.value)
}

var a, b value
wg.Add(2)
go printSum(&a, &b)
go printSum(&b, &a)
wg.Wait()
$ go run sample.go
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc000014054)
        /usr/local/go/src/runtime/sema.go:56 +0x39
sync.(*WaitGroup).Wait(0xc000014054)
        /usr/local/go/src/sync/waitgroup.go:130 +0x64
main.main()
        /Users/yuyabu/Documents/Study/go/chap1/training2/training2.go:34 +0x122

goroutine 5 [semacquire]:
sync.runtime_SemacquireMutex(0xc000014074, 0x0)
        /usr/local/go/src/runtime/sema.go:71 +0x3d
sync.(*Mutex).Lock(0xc000014070)
        /usr/local/go/src/sync/mutex.go:134 +0xff
main.main.func1(0xc000014060, 0xc000014070)
        /Users/yuyabu/Documents/Study/go/chap1/training2/training2.go:24 +0xa1
created by main.main
        /Users/yuyabu/Documents/Study/go/chap1/training2/training2.go:32 +0xea

goroutine 6 [semacquire]:
sync.runtime_SemacquireMutex(0xc000014064, 0x0)
        /usr/local/go/src/runtime/sema.go:71 +0x3d
sync.(*Mutex).Lock(0xc000014060)
        /usr/local/go/src/sync/mutex.go:134 +0xff
main.main.func1(0xc000014070, 0xc000014060)
        /Users/yuyabu/Documents/Study/go/chap1/training2/training2.go:24 +0xa1
created by main.main
        /Users/yuyabu/Documents/Study/go/chap1/training2/training2.go:33 +0x114
exit status 2
  • 上記のプログラムはsleep関数を抜くとdeadlockが発生しにくくなる。

  • 複数のロックを取るときは、同じ順番で取ることが重要。

  • Edgar Coffmanのdeadlock検知の論文が挙げられている

golangで1ミリ秒ごとにxxするという書き方

golangでこの構文は何か?という話題になった。

for range time.Tick(1 * time.Millisecond) {
  cadence.Broadcast()
}
  • 1ミリ秒待ってブロードキャスト、という意味になる。
  • 無限ループの先頭にsleep()関数を使うことでもかけるが、チャネルを使ってこのように書くこともできる
  • 上記ソースのtime.Tickのチャネルは閉じなくていいのか?
    • 残るけど、mainが死んだ時に全員死ぬのでOK

condとmutexの違い

  • condは外部からbroadcast,signalで起こすことができる。
  • broadcastとsignalの違いは全てのcondを起こせるか、どうかの違いがある。
cadence := sync.NewCond(&sync.Mutex{})
go func() {
  for range time.Tick(1 * time.Millisecond) {
    cadence.Broadcast()
    //broadcastは全てが起きる
    //

  }
}()

takeStep := func() {
  cadence.L.Lock()
  cadence.Wait()
  //waitじにはロックを解除する。、waitを抜けたときはlockを獲得している。
  //ブロードキャスト、もしくはシグナルの受診時にロックを獲得した状態でwait()のいちに戻ってくる
  //なお復帰、ロック再獲得はatomicに実行される
  cadence.L.Unlock()
}

  • Juliaは言語仕様にchanelがあるらしい

次回は2.3「これがどう役に立つのか」(p.29)から

yokohama-go-reading.connpass.com