2018/10/22

Recent entries from same category

  1. 画像に再生マークつけるだけのコマンド作った。
  2. Go言語で Lisp 処理系を作った。
  3. Go 言語のスライス挿入ベンチマーク
  4. Go 言語で変数のシャドウイングを避けたいなら shadow を使おう。
  5. Go 言語の struct の実体を引数で(なるべく)渡せない様にするテクニック

O'Reilly Japan, Inc. 様に献本頂きました。ありがとうございます。

そして献本頂く際にお声を掛けて頂いた、本書の翻訳を担当された ymotongpoo さんにもお礼を申し上げます。ありがとうございます。

本書の訳は非常に素晴らしく、とても原文が英文であったとは思えないほど綺麗で、読んでいく中で「原文でどの様に表現されているんだろう」といった引っかかりも無く、とてもスムーズに読み進められました。

Go 言語に関わって随分と長くなってきました。初めて Go を知ってからユーザがどんどん増える様を見る事が出来るのは正直に言って非常に嬉しいです。

ふと Go の魅力は何かと聞かれたら幾つか挙げる事が出来ますが、間違いなく選ぶのが「非同期処理の簡単さ」です。これまで多くの開発者が OS スレッドで実現してきた非同期処理を、Go 言語は少ないイディオムとインテリジェントなランタイムを使って誰でも簡単に非同期処理を実装できる仕組みを提供しています。

一般的に非同期処理の実装が難しいと言われるのには理由があります。それは直面している、ボトルネックがどのパターンで発生しているのか、また解決するにはどの手法を選ぶかの選択肢が多すぎるのです。

どこを関数に切り出してスレッドにし、どうやってデータを受け渡し、どうやってスレッドを待合せたら良いのか、慣れたプログラマでもそこそこ難しい問題です。

もしあなたが誰かに仕事を依頼し、その間に自分の仕事をしたかったとしましょう。「これで自分の仕事に集中できる」そう思うかもしれません。しかし依頼した仕事が終わればそれを手元の作業に取り込みたいとも思うでしょう。どうやって依頼した仕事の完了を待ったら良いでしょうか?都度、仕事が終わってないか確認しますか?それとも依頼した人に仕事の完了を伝えて貰いますか?もしかすると依頼した人には小まめにヒントを与える必要があるかも知れません。やりかたは様々です。一番効率の良い手法を選ばなければなりません。

Go の非同期処理は CSP (Communicating Sequential Processes) という並行処理の為の設計理論を元にしており、同期型のメッセージパッシングを使う事で CPU の負荷を抑えつつ各非同期処理を分かりやすく実現しています。このメッセージパッシングが goroutine と channel という言語レベルで記述可能というのが Go の特徴です。例えば channel を使って入力を2つに分ける tee を実装するのであれば以下の様に簡単に書け、しかも読む人にも分かりやすいのです。

func tee(in chan string) (<-chan string<-chan string) {
    out1, out2 := make(chan string), make(chan string)
    go func() {
        defer close(out1)
        defer close(out2)
        for v := range in {
            out1, out2 := out1, out2
            for i := 0; i < 2; i++ {
                select {
                case out1 <- v:
                    out1 = nil
                case out2 <- v:
                    out2 = nil
                }
            }
        }
    }()
    return out1, out2
}
ちなみに tee が何故 tee と呼ばれているかというと、入力と出力の枝が T になっているからです。(豆知識)

本書ではあり得る非同期のパターンの多くを分かりやすく解説してくれています。そして Go 言語では非同期処理をどの様に解決できているかを詳しく紹介してくれています。時折混ぜられるユーモアのある話もなかなか良かったのですが、僕が思うにこの本の良い所は、Go 言語で悩むであろう並行処理の設計方針について敢えて問題を読者に問い掛け、そして具体例で解き明かしつつ説明している所だと思います。そして多くのページを割いて間違った使い方についても紹介されています。どうするとデッドロックが起きてしまうのか、どうするとライブロック(道で向かいから来る人と同じ方向に避けてしまうアレです)が起きてしまうのか、なぜこのコードはスケールしないのか、多くのシーンをコード付きで説明してくれています。

特に興味深かったのが、channel を使うべきか、sync を使うべきかの決定木の図でした。yes/no に従いどちらを使うべきかをアドバイスしてくれます。この記事では紹介しませんが、ぜひ本書を読んで確認して頂くと良いかと思います。

一つ言っておくと、本書を読んでも Go での非同期処理のコードがスラスラ書ける様にはならないと思います。ただ今までなんとなく分かった気分で書いてしまっていた非同期処理が明確な理由を持って理解できる様になると思います。

Go を触り始めの方が多くハマるであろうケースも沢山書かれています。例えば Go の非同期処理に初めて触れた人は、きっと無限に goroutine を作り続けてしまうかもしれません。また Go が少し分かってくると、重たい処理を goroutine で起動できる様になりますが、goroutine とメイン goroutine で同時に変数を触ってしまい不整合を起こしてしまうというケースもあります。さらに Go が分かってきたとしても、使いどころを間違ってしまい goroutine を使ってヌルヌル動く様にはなったけれど結果としてパフォーマンスが大して速くならないといったケースもあるでしょう。Go を使ったとしても非同期処理を書くのは難しいのです。それでも Go が多くの人から評価されているのは少ないイディオムで多くの非同期パターンを記述できるからなのです。

goroutine と channel だけ使えば多くの非同期パターンを記述できます。

Big Sky :: Go 言語の非同期パターン

Go は goroutine という非同期の仕組みを提供していますが、使い方次第では色々なパターンが実装できる為、初めて goroutine を見た人はどの様な物が正解なのか分からない事があります。以...

https://mattn.kaoriya.net/software/lang/go/20180531104907.htm

2種類のイディオムだけでこれだけのパターンを書けるのです。とは言っても goroutine と channel で記述するとタイムアウトやキャンセルと言った処理は冗長になり得ます。Go はこの問題に context パッケージを提供する事で解決しているのですが、本書ではこの context が生まれた経緯や、なぜこの context が goroutine/channel の問題を解決できるのかといった根底の話も書かれています。

非同期処理が苦手な人が読むと少しクラクラするかもしれませんが、全体を通してもとてもリズムが良く、解説の順番も「上手いな」と思える内容でとても良かったです。そして今 Go 言語を書いている皆さんは今後も goroutine と channel を使って多くのコードを書くと思います。そのコードが何故必要になるのか、この非同期処理でどれほど効果が得られるのか、なぜスケールしないのか、そういった事を学びたい人らば必読の本だと思います。

Posted at by