JA EN
体系並列・分散
·★ 会員·12分で読めます

並行処理を1から — ロック・アトミック・メモリモデル

データ競合はなぜ起きるのか、なぜテストで再現しないのかを前提知識ゼロから解説。ロック・アトミック操作・CAS・メモリモデルまで、比喩と式とコードで順に積み上げます。

対象textタスクalgorithm

台所がひとつ、料理人がふたり

冷蔵庫に卵が10個。壁のメモには「残り: 10」と書いてあります。料理人の手順は「メモを読む → 卵を1個取る → 読んだ数から1を引いてメモを書き直す」。

1人ずつなら何も起きません。2人が同時に動くと壊れます。AもBもメモを読んで「10」。2人とも卵を取り、2人とも「9」と書き直す。冷蔵庫には8個しか残っていないのに、メモは9個。誰も手順を間違えていないのに、帳簿だけが合わなくなりました。

これがデータ競合です。卵が変数、メモがメモリ、料理人がスレッドにあたります。厄介なのは「たまに」しか起きないこと。タイミングが少しずれれば正しい答えが出るので、テストは通り、手元では再現せず、本番の混み合う時間帯にだけ帳簿が狂います。

なお並行(concurrency)は「複数の仕事の期間が重なる」という構造の話で、コアが1つでも成立します。並列(parallelism)は「物理的に同時に走る」という実行の話です。この記事のバグは並行の側から生まれます。コアが1つでも、OSがスレッドを途中で切り替える以上、同じことが起きるからです。

「1増やす」は1手ではない

いちばんよく壊れるのはこの1行です。

counter += 1

見た目は1動作ですが、機械語では最低3手に分かれます。メモリから読む(load)/レジスタで足す(add)/メモリに書く(store)。料理人と同じ「読んで・変えて・書く」(read-modify-write)で、この3手の途中に他のスレッドが割り込めます。2つのスレッドが10万回ずつ実行しても合計は20万になりません。失われた更新(lost update)の分だけ目減りし、実行のたびに違う数が出ます。

データ競合(data race)とは、「2つ以上のスレッドが同じメモリ位置にアクセスし、少なくとも一方が書き込みで、その間に順序づけがない」状態を指します。C/C++ の規格では、これを含むプログラムの動作は未定義です。コンパイラは競合が無い前提で最適化してよいので、「たまたま正しい値が出た」は何の保証でもありません。

似た言葉の競合状態(race condition)はもっと広く、タイミング次第で結果が変わる設計バグ全般を指します。個々の操作をロックで守っても、「残高を確認してから引き落とす」の合間に割り込まれれば残高はマイナスになりえます。守るべき区間を決めるのは人間の仕事です。

なぜ再現しないのか — 順番の組合せ爆発

再現しないのは運ではなく数の問題です。2つのスレッドがそれぞれ nn 手の命令を持つとき、起こりうる実行順(インターリーブ)の総数は、2つの列をトランプのように切り混ぜる方法の数になります。

(2nn)=(2n)!n!n!\binom{2n}{n} = \frac{(2n)!}{n!\,n!}
(1)

つまり「全体 2n2n 手のうち、どの nn 手をスレッド1の番に割り当てるか」を選ぶ数え方です。n=10n=10 で 184,756通り、n=20n=20 なら約1,378億通り。実際の関数は1つで数百手ありますし、スレッドが3本4本と増えれば桁はさらに跳ねます。そのうち壊れるのは、ほんの数通りかもしれません。テストを1回走らせるのは、この山からカードを1枚引くことに相当します。引いて当たらなかったことは、当たりが無いことの証明になりません。

FIG 1赤い O(2ⁿ) の伸び方を見てほしい。インターリーブの総数 C(2n,n) はこれよりさらに速く増える。テスト1回はこの山からカードを1枚引く行為で、「通った」は「当たりが無い」の証明にならない

さらに性質が悪いのは、観測すると消えることです。print を1行足す、ブレークポイントを張る、ログを増やす。どれもスレッドの実行時間を変えるので、当たりを引く確率そのものが動きます。「ログを足したら直った」は、直ったのではなく確率が下がっただけです。この種のバグはハイゼンバグと呼ばれます。

だから並行処理では「動いたから正しい」という普段の基準が使えません。実行して確かめるのではなく、構造として競合が起こりえないことを示すしかない。以下はそのための道具です。

素直な解決は、料理人を1人ずつしか台所に入れないことです。これがミューテックス(mutex, 相互排除)です。

この先にあるもの

§

ここから先は会員限定です

解説記事371本・教科書26章・学生モード48単元・論文精読6本が、月額¥490ですべて読み放題になります。新しい解説は毎日3本ずつ増えます。いつでも解約でき、解約後も期間の終わりまで読めます。

会員の方はログインすると続きが表示されます

コメント

コメントにはログインが必要です