読者です 読者をやめる 読者になる 読者になる

Goでスケールする実装を書く、を読んで

コチラの記事の感想というかなんというか、です。

Goでスケールする実装を書く - GolangRdyJp

なんつーか、概ね伝えたいコトは同意できるんですけど、用語の誤用っぽいモノがあってスッとアタマに入ってこないのです。
# ワタシの解説がアヤシイ場合は是非コメント等でツッコんでいただけるとウレシイです!

"冪等性"について

ある実装が他のコアやプロセス、他のホストで稼働中の場合、 どこにある実装で処理を実行したとしても 引き渡す情報が同じなら結果も同じであることを「べき等」であるという。

このような実装の場合、いくつもその実装が動く環境を用意しておいて、 順番に使われていない実装に丸投げするように分散させることで スケールして大きく性能を確保できる。

冪等性って「繰り返し同じ処理を行っても、結果が一緒」ってことで、数学的にいうと {f(x) = f(f(x))}だし、 chefとかでいうと「とある状態のサーバ」に何度「cookbookを適用」しても「最終的な結果は一緒」ということです。

冪等性があるからといって「何度繰り返しても」ってトコが並列化できるか、というとそんなことはないですよね?

じゃあ元記事でいいたいことはなにかっていうと、たぶん「参照透過性」のコトだと思います

元記事の全体通して s/べき等性/参照透過性/g として読んでみると、そこの分の違和感はなくなります。

"Lock-Free/Wait-Free"について

いや、もう、なんか、えーっと。。。

  • 排他制御を経由しない。(これはWait-Free扱い)
  • 排他制御はあるが待ちに入る状況にならない。

その説明だと、排他制御内の処理を(並列で)実行しないっていってるようなもんで、なんだかなぁと。


Lock-Freeは言葉通り「スレッドがロックしないこと」ということではあるんですけど、もうちょっと具体的にいうと 「mutexとかの排他処理機構をつかうと同時に実行されるとlockしちゃうから使わないようにしよう!」ということです。

Wait-Freeに関しては「他のスレッドの動作に関係なく、有限のステップで操作を完了させられること」なんですけど、具体的な説明は難しいので雑に説明すると「Lock-Freeかつ、"CAS操作を成功するまで繰り返す"みたいな処理がないこと」です。

そんなわけで、そもそも排他制御があったらLock-Freeって言わないと思いますし、Wait-Freeでもないと思います。

"リードオンリーなデータ参照のLock-Free化"について

いや、readonlyな(言い換えるとimmutableな)データの参照はそもそもスレッドセーフですよね?
Lock-Free云々とか文脈でアレコレ言及する必要は全くないかとー。

なお、まったく関係ないですけど、将来的に「NUMAアーキテクチャに対応するため、readonlyなデータは安全にコピーできるので、各goroutineにコピーを持つようにしよう」とか言われる時代がくるんですかね? どうですかね? 教えてエライヒト!

あと「変更するgoroutineが一つで他が参照だけ」というのは、とても有効なパターンだと思います。
他のgoroutineからの変更を考えなくてよいのでWait-Freeにできる(CAS操作の元の値のチェックでの失敗がなくなる)ので。

……という元記事とはまったく違う解説をしてみるテスト


なお「変更するgoroutineが一つ」という場合でも、単純に変数に値を代入するだけだと危険なケースがあります。他goroutineから参照されるケースでは黙ってatomicパッケージのStore系の操作を使いましょう。
# 詳細はgoのメモリモデルを見てください。

そんなこんなで、、、

ここまでツッコんでおいてアレなんですけど、個人的に並列化しやすいアプリを組む場合に気をつけてることは、というと……

  • mutableよりimmutable.
  • 参照透過性を意識して実装する
  • 排他制御をできるだけ排除する
    • できればLock-Freeに。
    • さらに可能ならWait-Freeに
  • 排他制御が必要なら、その範囲をできるだけ狭く本当に必要な分だけにする

という元記事の結論と大差ない内容なのですね。

ですので、元記事は説明がアレで本当にもったいないなぁと思う次第でありまして……