スレッドは並列実行のコンテキストを提供し、同期プリミティブはどの操作が同時に発生可能か、どの結果が他の操作より先に可視化されるべきかを制約します。Rust の型システムは不正な共有の多くを防げますが、ロック順序、終了プロトコル、マルチステップなビジネスロジックの原子性については依然として設計が必要です。
通常のスレッドとスコープスレッド
thread::spawn のクロージャーは通常 Send + 'static を要求し、JoinHandle を返します。join は結果を待機して観測しますが、ハンドルをドロップしてもスレッドの待機やキャンセルは自動的に行われません。スレッドスタックサイズのデフォルト値はプラットフォーム実装に依存し、必要に応じて Builder::stack_size で設定します。これはクロスプラットフォームで固定される定数ではありません。
スレッドが確実に現在のスコープ内で終了する場合は、外側のオブジェクトを借用できます:
fn main() {
let values = [1_u32, 2, 3];
let sum = std::thread::scope(|scope| {
let worker = scope.spawn(|| values.iter().sum::<u32>());
worker.join().unwrap()
});
assert_eq!(sum, 6);
}
scope は、スコープが返される前に関連するスレッドが必要な join を完了することを保証するため、非 'static の借用を使用できます。依然としてスレッドの panic を処理する必要があり、Send や借用ルールを回避するためにスコープ形式を使うことはできません。thread::scope
Mutex と RwLock が保護するのはアクセスの区間
MutexGuard が生存している間はロックが保持され、通常のデストラクションやアンワインド時に解放されます。panic=abort やプロセスの強制終了などでは、すべてのデストラクタが実行されることが保証されません。クリティカルセクションの境界を明確にし、ロック保持中にコールバックの実行、長時間の I/O、または逆方向の依存関係にあるロックの待機を避けてください。
標準ライブラリの Mutex のポイズニングは、未完成の更新が存在する可能性を示すヒントであり、回避不可能な安全バリアではありません。PoisonError からガードを取り戻すことは可能ですが、不変条件を評価するまでは回復しないでください。panic の検出も完全ではないため、unsafe 抽象は UB を防ぐためにポイズンだけに依存することはできません。Mutex
RwLock は複数のリーダーまたは 1 つのライターを許可しますが、公平性はプラットフォーム実装に依存し、読み取りが多い書き込みが少ない場合に Mutex より必ずしも高速になるとは限りません。読み込みロックの保持中にアップグレードを待ったり、ライターが待機している間に再帰的に読み込みロックを取得したりすると、予測不可能なブロッキングを引き起こす可能性があるため、明確な操作順序を採用してください。
チャンネル:所有権の移動とクローズ設計
std::sync::mpsc はブロッキングインターフェースで、マルチプロデューサ・シングルコンシューマです。channel は無制限のキューイングセマンティクスを提供し、sync_channel は制限付きバッファを提供します。容量 0 は合流(join)に使用できます。非同期タスクでは、ワーカーをブロックしないよう適切な非同期チャンネルを使用してください。
最後のメッセージを送信しても、チャンネルがクローズされたわけではありません。すべての sender が解放された後、受信側はキューに残っているメッセージをすべて読み終えてから、recv は切断エラーを返します。使用しない sender のクローンが残っていると、待機が終了しなくなります。受信側がクローズされると送信は失敗する可能性があるため、永遠にコンシューマーが存在すると仮定するのではなく、ワークを回収してください。
メッセージのやり取りは、内部で「メモリが完全に共有されていない」ことを意味しません。これは、アプリケーションが値と所有権に基づいて協調を整理するためのインターフェースを提供するものであり、基盤には依然として共有キューと同期機構が使われている可能性があります。
送信の成功がビジネス処理の完了を意味しない
use std::{sync::mpsc, thread};
fn main() {
let (work_tx, work_rx) = mpsc::sync_channel(0);
let (done_tx, done_rx) = mpsc::channel();
let worker = thread::spawn(move || {
let value: String = work_rx.recv().unwrap();
let length = value.len();
done_tx.send(length).unwrap();
});
work_tx.send(String::from("job")).unwrap();
assert_eq!(done_rx.recv().unwrap(), 3);
worker.join().unwrap();
let unsent = work_tx.send(String::from("next")).unwrap_err().0;
assert_eq!(unsent, "next");
}
容量 0 の send は受信操作と合流しますが、受信後にも処理作業が残ります。ビジネスの完了を知りたい場合は、確認応答または結果を返さなければなりません。バッファ付きチャンネルの send を処理の成功と見なしてはいけません。SendError<T> は送信できなかった T を呼び出し元に返すため、呼び出し元は保存、再ルーティング、またはレポートを行うことができ、失敗パスでの所有権の不明確な消失を防ぐことができます。sync_channel
制限付きチャンネルもデッドロックに関与する可能性があります。送信者が空きを待っている間に、コンシューマーがメッセージを処理するために必要なロックを保持している場合、コンシューマーはキューの圧力を解放できません。同期設計では、ロックとチャンネルの依存関係を一緒に確認する必要があり、ロックの取得順序だけを調べているだけでは不十分です。
Condvar:まず述語があり、次に通知がある
use std::sync::{Condvar, Mutex};
use std::thread;
fn main() {
let shared = (Mutex::new(false), Condvar::new());
thread::scope(|scope| {
let waiter = scope.spawn(|| {
let (lock, condition) = &shared;
let guard = lock.lock().unwrap();
let ready = condition.wait_while(guard, |ready| !*ready).unwrap();
assert!(*ready);
});
let (lock, condition) = &shared;
*lock.lock().unwrap() = true;
condition.notify_one();
waiter.join().unwrap();
});
}
通知が待機スレッドが正式にスリープする前に発生した場合でも、その後 true を確認すると待機しないため、「その通知をたまたま受信した」ことに依存しません。wait_while はループチェックを記述し、疑似起床や他の待機スレッドが先に状態を消費する競合に対処します。Condvar
通知はセマフォの許可ではなく、将来 wait を呼び出すスレッドのために回数を保存しません。1 つの Condvar は、インターフェースの要件に従って同じ関連ロックと組み合わせて使用する必要があります。状態のチェックと変更は共通のプロトコルに従わなければなりません。
「今後結果がない」ことも述語に含める
ready フラグのみの待機プログラムでは、プロデューサが事前に失敗した場合、コンシューマーは永遠に true を待たされる可能性があります。以下では、待機、完了、クローズの 3 つの状態を区別します。クローズ自体も待機スレッドに通知する必要があります。
use std::{sync::{Condvar, Mutex}, thread};
#[derive(Debug)]
enum State { Waiting, Ready(u32), Closed }
fn receive(shared: &(Mutex<State>, Condvar)) -> Option<u32> {
let (lock, condition) = shared;
let mut state = condition.wait_while(lock.lock().unwrap(),
|state| matches!(state, State::Waiting)).unwrap();
match std::mem::replace(&mut *state, State::Closed) {
State::Ready(value) => Some(value),
State::Closed => None,
State::Waiting => unreachable!("述語は待機条件を満たしていない"),
}
}
fn run(result: Option<u32>) -> Option<u32> {
let shared = (Mutex::new(State::Waiting), Condvar::new());
thread::scope(|scope| {
let receiver = scope.spawn(|| receive(&shared));
*shared.0.lock().unwrap() = match result {
Some(value) => State::Ready(value), None => State::Closed,
};
shared.1.notify_one();
receiver.join().unwrap()
})
}
fn main() {
assert_eq!(run(Some(42)), Some(42));
assert_eq!(run(None), None);
}
これはシングルプロデューサ・シングルコンシューマ・1 回限りの配信のローカルモデルであり、汎用チャンネルではありません。複数の待機スレッドに対するクローズでは通常 notify_all が必要であり、状態遷移後に誰がまだ待機可能かを再考する必要があります。プロデューサの panic は自動的に Closed にはならないため、所有者がクローズを担当するか、切断セマンティクスを持つチャンネルを採用する必要があります。
タイムアウト後もロックで保護された状態を確認する
wait_timeout が返す際にはロックが再取得され、起床の原因は通知、タイムアウト、または疑似起床のいずれかです。手動でループを書く場合、毎回 5 秒の完全な待機を再実行すると、連続する起床により総待機時間が大幅に延びる可能性があります。wait_timeout_while を使用して総時間を管理するか、固定された期限を保存して残り時間を計算してください。
use std::{sync::{Condvar, Mutex}, time::Duration};
fn main() {
let ready = Mutex::new(false);
let condition = Condvar::new();
let (state, timeout) = condition.wait_timeout_while(
ready.lock().unwrap(), Duration::ZERO, |ready| !*ready,
).unwrap();
assert!(!*state);
assert!(timeout.timed_out());
}
ここでは、スレッドのスケジューリング速度に依存せずに、満たされていない条件のタイムアウトブランチを検証するために 0 予算を使用しています。実際のタイムアウトも、正確な返却時刻を保証するものではありません。待機スレッドはロックの再取得と CPU の獲得を必要とするためです。最終的に結果が利用可能かどうかを決定するのは、述語であり、「通知を受信した」ことや「期限に達した」ことだけでは不十分です。Condvar のタイムアウト
アトミック変数:アトミック性は他のデータの公開を自動的に行わない
Relaxed はそのアトミック操作自体を保証するだけで、必要なクロスオブジェクトの公開関係を確立しません。Acquire は、対応する Release 書き込みまたは適用される解放シーケンスを観察した時点で初めて同期を確立します。2 つのスレッドでそれぞれ Acquire/Release キーワードを書くだけでは不十分です。
以下では、2 つのアトミック変数を使用して 1 回の公開を示します。ペイロード自体もアトミック型を使用し、教学コードに同期されていない通常のメモリアクセスが含まれないようにしています:
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::thread;
fn main() {
let payload = AtomicUsize::new(0);
let ready = AtomicBool::new(false);
thread::scope(|scope| {
scope.spawn(|| {
payload.store(42, Ordering::Relaxed);
ready.store(true, Ordering::Release);
});
while !ready.load(Ordering::Acquire) {
thread::yield_now();
}
assert_eq!(payload.load(Ordering::Relaxed), 42);
});
}
ready=true を読み取る Acquire は、それを公開した Release とペアになり、それ以前の payload の書き込みは後の読み取りに先行します。この例では 1 回の公開のみ行っています。同じフラグを繰り返し再利用する場合は、世代管理或其他のプロトコルが必要であり、単純にリセットして競合がないと仮定することはできません。
SeqCst は、対応する順序一貫性のあるアトミック操作に単一の全順序の要件を追加しますが、任意の通常の変数を自動的にアトミックにするものではなく、複数の操作をトランザクションとしてまとめるものでもありません。compare_exchange の成功と失敗のメモリ順序は異なります。失敗は読み取りに過ぎないため、書き込み方向の順序を任意に使用することはできません。Ordering
順序一貫性は複合操作の分割不能性を意味しない
use std::{sync::{Barrier, atomic::{AtomicUsize, Ordering}}, thread};
fn main() {
let count = AtomicUsize::new(0);
let barrier = Barrier::new(2);
thread::scope(|scope| {
for _ in 0..2 {
scope.spawn(|| {
let old = count.load(Ordering::SeqCst);
barrier.wait();
count.store(old + 1, Ordering::SeqCst);
});
}
});
assert_eq!(count.load(Ordering::SeqCst), 1);
count.store(0, Ordering::Relaxed);
thread::scope(|scope| {
for _ in 0..2 {
scope.spawn(|| { count.fetch_add(1, Ordering::Relaxed); });
}
});
assert_eq!(count.load(Ordering::Relaxed), 2);
}
Barrier は、書き込み前に両側が読み取りを完了することを保証するため、丢失更新は確定的であり、実行を繰り返して運に頼る必要はありません。2 番目の fetch_add は、インクリメントを読み取り・変更・書き込みのアトミック操作として行います。ここではカウントが他のデータを公開するために使用されておらず、最終的な観測がスコープスレッドの完了後に行われるため、Relaxed は必要なカウントの原子性を表現するのに十分です。fetch_add の整数オーバーフローはラップアラウンドとして処理されます。カウントがラップアラウンドできない場合は、チェック付きの更新プロトコルを使用してください。AtomicUsize
Barrier は、参加者の数とフェーズが明確なシナリオにのみ適しています。ある参加者が到着前に早期に返却したり panic したりした場合、他のスレッドは永遠に待機する可能性があります。Barrier 自体はビジネスの終了回復を提供しません。また、ミューテックスでもありません。バリアを通過した後、各スレッドは同時に継続できます。
各操作が原子的でも更新を失う理由
分離した各読み書きが SeqCst の原子操作でも、両スレッドが同じ旧値を先に読めます。その順序を作ってから fetch_add に変えると、解決策は強いメモリ順序ではなく単一の原子的な読み取り・変更・書き込みだと分かります。このカウンタは他データの公開には使いません。
図を準備しています
各操作が原子的でも更新を失う理由load と store は 2 操作です。両方が 0 を読んで 1 を書くと、最終値も 1 です。
{"id":"rust-atomic-lost-update","title":"各操作が原子的でも更新を失う理由","summary":"load と store は 2 操作です。両方が 0 を読んで 1 を書くと、最終値も 1 です。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"mode\"><button data-mode=\"split\" data-i18n=\"split\"></button><button data-mode=\"rmw\" data-i18n=\"rmw\"></button></div><div class=\"actions\"><button id=\"aRead\" data-i18n=\"aRead\"></button><button id=\"aWrite\" data-i18n=\"aWrite\"></button><button id=\"bRead\" data-i18n=\"bRead\"></button><button id=\"bWrite\" data-i18n=\"bWrite\"></button><button id=\"aAdd\" data-i18n=\"aAdd\"></button><button id=\"bAdd\" data-i18n=\"bAdd\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><div class=\"metrics\"><div><span data-i18n=\"memory\"></span><strong id=\"memory\"></strong></div><div><span data-i18n=\"local\"></span><strong id=\"local\"></strong></div></div><section class=\"panel\"><h3 data-i18n=\"history\"></h3><div id=\"history\"></div></section><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"explain\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:550;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:550 20px ui-monospace,monospace}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px ui-monospace,monospace;background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 ui-monospace,monospace;white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px ui-monospace,monospace}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 ui-monospace,monospace;white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:ui-monospace,monospace}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px ui-monospace,monospace;padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 ui-monospace,monospace;overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.chart-wrap{margin:18px 0}.chart-label{font-size:12px;color:var(--muted);min-height:3.2em}.curve{display:block;width:100%;height:auto;max-height:320px;overflow:visible}.curve text{font:11px ui-monospace,monospace;fill:var(--muted)}.gridline{stroke:var(--rule);stroke-width:1}.metrics strong{font-size:20px;min-height:3.2em}.formula{font:12px/1.8 ui-monospace,monospace;min-height:9em}.readout{min-height:8em}.presets button{flex-basis:30%}.scope p{overflow-wrap:anywhere} .unit-note{font-size:12px;color:var(--muted);margin:10px 0}\n\n.metrics span{word-break:break-all;line-break:strict}.metrics>div{padding-right:6px}\n\n.cell{min-height:72px}.metrics strong{min-height:3.6em}\n\n#history{min-height:8em;font:13px/1.8 ui-monospace,monospace;white-space:pre-line}.metrics strong{font-size:17px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';row.style.gridTemplateColumns='repeat('+r.length+',minmax(0,1fr))';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function plot(id,series,xr,yr,opts={}){const e=$('#'+id),X=x=>42+(x-xr[0])/(xr[1]-xr[0])*298,Y=y=>175-(y-yr[0])/(yr[1]-yr[0])*148;let out='<defs><clipPath id=\"clip-'+id+'\"><rect x=\"42\" y=\"27\" width=\"298\" height=\"148\"/></clipPath></defs>';for(let i=0;i<3;i++){let x=xr[0]+i*(xr[1]-xr[0])/2,y=yr[0]+i*(yr[1]-yr[0])/2;out+='<path class=\"gridline\" d=\"M '+X(x)+' 27V175M42 '+Y(y)+'H340\"/><text x=\"'+X(x)+'\" y=\"194\" text-anchor=\"middle\">'+esc(opts.xfmt?opts.xfmt(x):Number(x.toFixed(2)))+'</text><text x=\"36\" y=\"'+(Y(y)+4)+'\" text-anchor=\"end\">'+esc(opts.yfmt?opts.yfmt(y):Number(y.toFixed(2)))+'</text>';}out+='<g clip-path=\"url(#clip-'+id+')\">';for(const s of series){out+='<path fill=\"none\" stroke=\"'+(s.color||'var(--accent)')+'\" stroke-width=\"2.5\" '+(s.dash?'stroke-dasharray=\"5 4\"':'')+' d=\"'+s.pts.map((p,i)=>(i?'L':'M')+X(p[0]).toFixed(2)+','+Y(p[1]).toFixed(2)).join(' ')+'\"/>';}for(const p of opts.points||[])out+='<circle cx=\"'+X(p[0])+'\" cy=\"'+Y(p[1])+'\" r=\"4\" fill=\"var(--second)\" stroke=\"var(--paper)\" stroke-width=\"1.5\"/>';if(opts.cursor!==undefined)out+='<path d=\"M'+X(opts.cursor)+' 27V175\" stroke=\"var(--muted)\" stroke-dasharray=\"3 3\"/>';e.innerHTML=out+'</g>';e.dataset.curves=JSON.stringify(series.map(s=>s.pts));}const curve=(fn,lo,hi,n=120)=>Array.from({length:n+1},(_,i)=>{const x=lo+(hi-lo)*i/n;return[x,fn(x)]});function inputs(draw){document.querySelectorAll('input,select').forEach(e=>{e.addEventListener('input',draw);e.addEventListener('change',draw)});draw()}const n=id=>+$('#'+id).value;const show=(id,v,d=3)=>set(id,Number(v.toFixed(d)));let mode='split',v=0,a=null,b=null,ad=false,bd=false,log=[];function init(){v=0;a=b=null;ad=bd=false;log=[];draw()}function draw(){pressed(mode);set('memory',v);set('local','A: '+(a??'—')+' / B: '+(b??'—'));set('history',log.join('\\n'));$('#aRead').disabled=mode!=='split'||a!==null;$('#bRead').disabled=mode!=='split'||b!==null;$('#aWrite').disabled=mode!=='split'||a===null||ad;$('#bWrite').disabled=mode!=='split'||b===null||bd;$('#aAdd').disabled=mode!=='rmw'||ad;$('#bAdd').disabled=mode!=='rmw'||bd;set('explanation',t('explain'));}for(let id of ['aRead','bRead','aWrite','bWrite','aAdd','bAdd'])$('#'+id).onclick=()=>{if(id==='aRead')a=v;if(id==='bRead')b=v;if(id==='aWrite'){v=a+1;ad=true}if(id==='bWrite'){v=b+1;bd=true}if(id==='aAdd'){v++;ad=true}if(id==='bAdd'){v++;bd=true}log.push(id+' → '+v);draw()};$('#reset').onclick=init;document.querySelectorAll('[data-mode]').forEach(e=>e.onclick=()=>{mode=e.dataset.mode;init()});draw();","audio":false,"strings":{"aAdd":"A:fetch_add","aRead":"A:load","aWrite":"A:store","bAdd":"B:fetch_add","bRead":"B:load","bWrite":"B:store","explain":"分離モードで A load、B load、A store、B store の順に操作します。強いメモリ順序でも 2 操作は統合されません。","heading":"各操作が原子的でも更新を失う理由","history":"実行履歴","intro":"2 スレッドの読み書き順を選び、fetch_add と比較します。","local":"スレッドの読み取り値","memory":"共有カウント","mode":"加算方法","reset":"リセット","rmw":"fetch_add","scope":"個別の load/store は SeqCst、独立カウンタの fetch_add は Relaxed です。各スレッドは 1 回加算します。","scopeLabel":"モデルの範囲と前提","split":"load → store"}}
Acquire が読んだ書き込みはどれか
Acquire だけでなく、どの書き込みを読んだかを追います。単発公開の同期辺を示し、false なら分岐せず、true でも Release または Acquire が欠ければ必要な保証はありません。payload も原子なので、同期がない比較も定義された動作内です。
図を準備しています
Acquire が読んだ書き込みはどれかここで payload の公開が成立するのは、Acquire が対応する Release を読んだ時です。
{"id":"rust-release-acquire-edge","title":"Acquire が読んだ書き込みはどれか","summary":"ここで payload の公開が成立するのは、Acquire が対応する Release を読んだ時です。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"check\"><input type=\"checkbox\" id=\"release\" checked><span data-i18n=\"release\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"acquire\" checked><span data-i18n=\"acquire\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"observed\" checked><span data-i18n=\"observed\"></span></label><section class=\"panel\"><h3 data-i18n=\"proof\"></h3><div id=\"proof\"></div></section><section class=\"panel\"><h3 data-i18n=\"guarantee\"></h3><div id=\"guarantee\"></div></section><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"ok\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"bad\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"skip\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:550;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:550 20px ui-monospace,monospace}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px ui-monospace,monospace;background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 ui-monospace,monospace;white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px ui-monospace,monospace}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 ui-monospace,monospace;white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:ui-monospace,monospace}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px ui-monospace,monospace;padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 ui-monospace,monospace;overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.chart-wrap{margin:18px 0}.chart-label{font-size:12px;color:var(--muted);min-height:3.2em}.curve{display:block;width:100%;height:auto;max-height:320px;overflow:visible}.curve text{font:11px ui-monospace,monospace;fill:var(--muted)}.gridline{stroke:var(--rule);stroke-width:1}.metrics strong{font-size:20px;min-height:3.2em}.formula{font:12px/1.8 ui-monospace,monospace;min-height:9em}.readout{min-height:8em}.presets button{flex-basis:30%}.scope p{overflow-wrap:anywhere} .unit-note{font-size:12px;color:var(--muted);margin:10px 0}\n\n.metrics span{word-break:break-all;line-break:strict}.metrics>div{padding-right:6px}\n\n.cell{min-height:72px}.metrics strong{min-height:3.6em}\n\n#proof{min-height:10em;font:13px/1.8 ui-monospace,monospace;white-space:pre-line}#guarantee{min-height:4em;font-size:15px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';row.style.gridTemplateColumns='repeat('+r.length+',minmax(0,1fr))';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function plot(id,series,xr,yr,opts={}){const e=$('#'+id),X=x=>42+(x-xr[0])/(xr[1]-xr[0])*298,Y=y=>175-(y-yr[0])/(yr[1]-yr[0])*148;let out='<defs><clipPath id=\"clip-'+id+'\"><rect x=\"42\" y=\"27\" width=\"298\" height=\"148\"/></clipPath></defs>';for(let i=0;i<3;i++){let x=xr[0]+i*(xr[1]-xr[0])/2,y=yr[0]+i*(yr[1]-yr[0])/2;out+='<path class=\"gridline\" d=\"M '+X(x)+' 27V175M42 '+Y(y)+'H340\"/><text x=\"'+X(x)+'\" y=\"194\" text-anchor=\"middle\">'+esc(opts.xfmt?opts.xfmt(x):Number(x.toFixed(2)))+'</text><text x=\"36\" y=\"'+(Y(y)+4)+'\" text-anchor=\"end\">'+esc(opts.yfmt?opts.yfmt(y):Number(y.toFixed(2)))+'</text>';}out+='<g clip-path=\"url(#clip-'+id+')\">';for(const s of series){out+='<path fill=\"none\" stroke=\"'+(s.color||'var(--accent)')+'\" stroke-width=\"2.5\" '+(s.dash?'stroke-dasharray=\"5 4\"':'')+' d=\"'+s.pts.map((p,i)=>(i?'L':'M')+X(p[0]).toFixed(2)+','+Y(p[1]).toFixed(2)).join(' ')+'\"/>';}for(const p of opts.points||[])out+='<circle cx=\"'+X(p[0])+'\" cy=\"'+Y(p[1])+'\" r=\"4\" fill=\"var(--second)\" stroke=\"var(--paper)\" stroke-width=\"1.5\"/>';if(opts.cursor!==undefined)out+='<path d=\"M'+X(opts.cursor)+' 27V175\" stroke=\"var(--muted)\" stroke-dasharray=\"3 3\"/>';e.innerHTML=out+'</g>';e.dataset.curves=JSON.stringify(series.map(s=>s.pts));}const curve=(fn,lo,hi,n=120)=>Array.from({length:n+1},(_,i)=>{const x=lo+(hi-lo)*i/n;return[x,fn(x)]});function inputs(draw){document.querySelectorAll('input,select').forEach(e=>{e.addEventListener('input',draw);e.addEventListener('change',draw)});draw()}const n=id=>+$('#'+id).value;const show=(id,v,d=3)=>set(id,Number(v.toFixed(d)));function draw(){let r=$('#release').checked,a=$('#acquire').checked,o=$('#observed').checked,paired=r&&a&&o;set('proof','T1: payload.store(42, Relaxed)\\n ↓ sequenced-before\\nT1: ready.store(true, '+(r?'Release':'Relaxed')+')\\n '+(paired?'↓ synchronizes-with':'⋮')+'\\nT2: ready.load('+(a?'Acquire':'Relaxed')+') = '+o+'\\n ↓ if true\\nT2: payload.load(Relaxed)');set('guarantee',!o?t('none'):paired?'42':t('unknown'));set('explanation',t(!o?'skip':paired?'ok':'bad'));document.body.dataset.paired=paired;}inputs(draw);","audio":false,"strings":{"acquire":"Acquire で読む","bad":"同期辺がなく、true を読んでも原子性だけで payload の公開は保証されません。","guarantee":"後続の payload 読み取りの保証","heading":"Acquire が読んだ書き込みはどれか","intro":"公開と読み取りのメモリ順序、読み取った ready 値を変えます。","none":"分岐を実行しない","observed":"今回の ready=true を読む","ok":"対応する公開を観測し、payload.store(42) が後続読み取りに happens-before します。","proof":"順序関係","release":"Release で公開","reset":"リセット","scope":"一度の公開で payload も原子です。同期がないとは保証がないことで、必ず古い値を読む意味ではありません。","scopeLabel":"モデルの範囲と前提","skip":"false なら payload の読み取り分岐へ進まず、Acquire は無条件の障壁ではありません。","unknown":"このプロトコルでは 0 も 42 も可能"}}
同期方法を選択する際は、待機と所有権を先に問う
カウントのみが必要で他の状態を公開しない場合は、Relaxed アトミックを検討してください。複数のフィールドが共通の不変条件を維持している場合、Mutex が証明しやすいことが多いです。複雑な述語を待機する場合は、ロックと Condvar を使用します。リソースの排他を 1 つの実行者に委ねる場合は、チャンネルを使用します。
スピンアップの例は順序を示すためのものであり、汎用的な待機戦略ではありません。長時間の待機には、適切なブロッキングまたは非同期通知メカニズムを使用してください。並行プログラムの検証では、正常な出力だけでなく、早期終了、クローズ、エラー、タスクの回収、ロックの依存関係も確認してください。
停止条件をプロトコルの一部とする
| 待機ポイント | 正常な進行条件 | 設計必須の停止条件 |
| チャンネルの受信 | 作業を受信 | すべての送信側がクローズ、明示的な終了メッセージ、または期限切れ |
| Condvar | ロックで保護された述語の変更 | クローズ/失敗も述語に入り、待機スレッドに通知 |
| Barrier | 十分な参加者が到着 | 参加者が中途半端に調整なしで退出しない |
| join | 対象スレッドの終了 | 作業ループが停止状態を観察でき、ブロッキング待機に退出経路がある |
AtomicBool の停止フラグだけでは、recv や Condvar でブロックされているスレッドを唤醒 できません。フラグの書き込みに加え、チャンネルのクローズ、通知の送信、またはタイムアウト付きの待機を使用して、スレッドが停止条件を再確認する機会を与える必要があります。join は単に終了を待つだけで、スレッドに対してキャンセル要求を発行するものではありません。
デッドロックのトラブルシューティングでは、まず待機関係を図にしてください。誰が誰がデータを提供するかやリソースを解放するかを待っているか、誰が相手の必要なリソースを保持しているか。単に sleep を繰り返したり、スレッド数を増やしたり、より強力なメモリ順序に変えたりしても、このプロトコル層の循環依存関係は通常修正できません。