STM
Software Transactional Memory
gpt-5.icon
ロックを直接使わずに安全な並行プログラミングを実現する仕組み。
目的
ロックの複雑さ・バグ(デッドロック、競合、副作用伝搬)を避ける
プログラマは処理を「普通の順序コード」のように書ける
衝突はランタイムが検出して自動的にリトライ
仕組み(最低限)
1. 開始
トランザクション開始時に現在のメモリ状態のスナップショットを持つ。
2. 読み取り
読み値は読みログに記録され、必要ならスナップショットから読み取る。
3. 書き込み
書きログに値を蓄積しておき、実メモリには反映しない。
4. コミット
他トランザクションが読みログの対象を書き換えていなければコミット。
競合があれば アボートして最初からリトライ。
これにより、プログラマがロックの粒度や取得順序を考える必要がなくなる。
メリット
デッドロックなし
順序通りのコードのまま並行化
コンポーザブル(複数のトランザクション関数を組み合わせても安全)
ロックより直感的で保守しやすい
デメリット
リトライコスト(高競合領域ではパフォーマンス悪化)
I/O と相性が悪い
トランザクション内の I/O は中断→巻き戻しができないため制約が大きい
実装コスト
メモリオーバーヘッド
Haskell STM(代表的実装)
Haskell の STM は代表的かつ洗練された実装で、以下の点で特に優れている。
特徴
atomically :: STM a -> IO a で原子化
トランザクション内でのみ実行できる TVar を用いたメモリ管理
retry や orElse による待ち合わせ・合成が可能
最小例
code:haskell
import Control.Concurrent.STM
transfer :: TVar Int -> TVar Int -> Int -> STM ()
transfer from to amount = do
balance <- readTVar from
if balance < amount
then retry
else do
writeTVar from (balance - amount)
writeTVar to (balance + amount)
retry により、条件が整うまで自動的に待ち合わせるというのがロックにない強い表現力。
STM vs ロックベース
table:_
項目 STM ロック
デッドロック 起こらない 起こりうる
合成性 高い 低い(2つの臨界区間を安全に合成するのは難しい)
パフォーマンス 中低競合なら高速 高競合はロックが有利
I/O 不向き 問題なし
lock機構に比べて、STMの方が初心者が理解しやすい研究結果がある、みたいな話をしている
Clojureにも機能としてある
Clojureのデータ構造はイミュータブルなので、情報を更新するためにはトランザクションを用いる