『Go言語で学ぶ並行プログラミング』の練習問題より

今回は読書会じゃなくて Bluesky からのネタ1

Go言語で学ぶ並行プログラミングの4章の hello world みたいな練習問題、すでにめっちゃ難しいんだけど、この答えの 26行目は lock とらないでいいの? (4.3 練習問題 1) github.com/cutajarj/Con...

[image or embed]

— ikawaha (@ikawaha.bsky.social) April 16, 2025 at 11:57 PM

「4.3 練習問題」の1というのがこれ。

リスト 4.15(もともとは第 3 章から)は、共有変数へのアクセスを保護するためのミューテックスを使っていません。これは悪い習慣です。このプログラムを変更して、共有変数 seconds へのアクセスをミューテックスで保護するようにしてください。ヒント:変数をコピーする必要があるかもしれません。

んで「リスト 4.15」というのがこちら:

package main

import (
    "fmt"
    "time"
)
func countdown(seconds *int) {
    for *seconds > 0 {
        time.Sleep(1 * time.Second)
        *seconds -= 1
    }
}

func main() {
    count := 5
    go countdown(&count)
    for count > 0 {
        time.Sleep(500 * time.Millisecond)
        fmt.Println(count)
    }
}

簡単なカウントダウンのプログラムだけど,カウントダウンを goroutine で実行しているのがポイント。 main()count 変数が2つの goroutine で使われているの(countdown() 関数側は seconds)がお分かりだろうか。

で,この問題の回答は GitHub リポジトリで公開されている。 こんな感じ:

package main

import (
    "fmt"
    "sync"
    "time"
)

func countdown(seconds *int, mutex *sync.Mutex) {
    mutex.Lock()
    remaining := *seconds
    mutex.Unlock()
    for remaining > 0 {
        time.Sleep(1 * time.Second)
        mutex.Lock()
        *seconds -= 1
        remaining = *seconds
        mutex.Unlock()
    }
}

func main() {
    mutex := sync.Mutex{}
    count := 5
    go countdown(&count, &mutex)
    remaining := count
    for remaining > 0 {
        time.Sleep(500 * time.Millisecond)
        mutex.Lock()
        fmt.Println(count)
        remaining = count
        mutex.Unlock()
    }
}

上で強調している行はロック取ってないよね,というのが最初のポストの内容というわけだ。

ちょっとややこしいのだが,もともとこの練習問題の回答としては

func main() {
    mutex := sync.Mutex{}
    count := 5
    go countdown(&count, &mutex)
    for count  > 0 {
        time.Sleep(500 * time.Millisecond)
        mutex.Lock()
        fmt.Println(count)
        mutex.Unlock()
    }
}

が提示されていたらしい(countdown() 関数は省略して main() 関数のみ挙げている)。 これだと for ループの条件文で count を直接参照しているので拙いよね,という指摘があったのだが,この指摘に対応しようとして失敗しているように見える。

おそらく main() 関数を修正するのであれば以下が妥当だと思う。

func main() {
    mutex := sync.Mutex{}
    count := 5
    remaining := count
    go countdown(&count, &mutex)
    for remaining > 0 {
        fmt.Println(remaining)
        time.Sleep(500 * time.Millisecond)
        mutex.Lock()
        remaining = count
        mutex.Unlock()
    }
}

まず remaining 変数の初期化宣言を goroutine 起動前に行う。 さらに for ループ内の fmt.Println()remaining 変数を参照するよう変更する(こうすることで fmt.Println() をロックの外に出せる。なお,上のコードではカウント 0 を出力しないよう fmt.Println() 関数の位置を変えている)。

といったところだろうか。

問題は最初の回答コードでも,その後の不完全修正コードでも,たぶん支障なく動いてしまうところなんだよね。 だってカウントダウンさせてる int 型の共有変数を参照してるだけだもん。 参照時に多少の不整合が起きても,おそらく見た目では分からない。

著者の方も指摘に対して

Thank you for spotting this. This is proof that race conditions are easy to miss!

と返しておられる通り,並行処理のデバッグがいかに難しいか分かる。 ただ,共有変数 count へアクセスする際に「不変式が真2」であることをきちんと保証するためには,この実装をサボってはいけない。

Go に詳しい方なら「for 文で回さなくても channel を使えばいいし,なんなら atomic パッケージも使えるぢゃん」と思うだろう(そしてそれは正しい)が『Go言語で学ぶ並行プログラミング』では4章でようやく sync.Mutex が登場したところで, channel は7章, atomic パッケージは12章で登場する。 あしからず。 なお『Go言語で学ぶ並行プログラミング』本編には言及が見当たらないが sync.Mutex は再入不可である3

参考図書

photo
Go言語で学ぶ並行プログラミング 他言語にも適用できる原則とベストプラクティス impress top gearシリーズ
James Cutajar (著), 柴田 芳樹 (著)
インプレス 2024-12-04 (Release 2024-12-04)
Kindle版
B0DNYMMBBQ (ASIN)
評価     

読書会のために購入。インプレス社の本は Kindle 版より版元で PDF 版を買うのがオススメ。「並行処理」について原理的な解説から丁寧に書かれている。 Go で解説されているが Go 以外の言語でも応用できる。

reviewed by Spiegel on 2025-01-25 (powered by PA-APIv5)

photo
プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
丸善出版 2016-06-20
単行本(ソフトカバー)
4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN)
評価     

著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。と思ったら絶版状態らしい(2025-01 現在)。復刊を望む!

reviewed by Spiegel on 2016-07-13 (powered by PA-APIv5)

photo
Go言語 100Tips ありがちなミスを把握し、実装を最適化する impress top gearシリーズ
Teiva Harsanyi (著), 柴田 芳樹 (著)
インプレス 2023-08-18 (Release 2023-08-18)
Kindle版
B0CFL1DK8Q (ASIN)
評価     

版元で PDF 版を購入可能。事実上の Effective Go とも言える充実の内容。オリジナルは敢えてタイトルに “tips” という単語を入れるのを避けたのに邦題が「100 Tips」とかなっていて,原作者がお怒りとの噂(あくまで噂)

reviewed by Spiegel on 2023-08-18 (powered by PA-APIv5)

photo
Go言語による並行処理
Katherine Cox-Buday (著), 山口 能迪 (翻訳)
オライリージャパン 2018-10-26
単行本(ソフトカバー)
4873118468 (ASIN), 9784873118468 (EAN), 4873118468 (ISBN)
評価     

Eブック版もある。感想はこちら。 Go 言語で並行処理を書くならこの本は必読書になるだろう。

reviewed by Spiegel on 2020-01-13 (powered by PA-APIv5)


  1. 𝕏 の TL はホンマにウザいので,エンジニアの方々は Bluesky に来て欲しい,マジで。 ↩︎

  2. 不変式の話は『プログラミング言語Go』に出てくる。また『Go言語で学ぶ並行プログラミング』の「訳者あとがき」でも『プログラミング言語Go』の内容を紹介する形で説明がある。でも『プログラミング言語Go』は絶版状態なんだよなぁ orz ↩︎

  3. 再入云々については『Go言語で学ぶ並行プログラミング』の「訳者あとがき」に解説がある。たとえば Java のミューテックスは再入可能なため,その辺の差異について認識しておく必要がある。 ↩︎