线程提供可并行的执行上下文,同步原语约束哪些操作可以同时发生、哪些结果必须先于其他操作可见。Rust 的类型系统能阻止许多非法共享,但锁顺序、终止协议和多步业务原子性仍需设计。
普通线程与作用域线程
thread::spawn 的闭包通常需要 Send + 'static,返回 JoinHandle;join 等待并观察结果,丢弃 handle 不会自动等待或取消线程。线程栈大小的默认值与平台实现有关,需要时用 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 的 poisoning 是提示可能存在未完成更新,而非不可绕过的安全屏障。PoisonError 可取回 guard,但恢复前要评估不变量;panic 检测也不是完整的,所以 unsafe 抽象不能仅依赖 poison 防止 UB。Mutex
RwLock 允许多个 reader 或一个 writer,公平性与平台实现有关,不保证读多写少就一定比 Mutex 快。持读锁时等待升级、或已有 writer 等待时再递归取读锁,都可能造成难以预期的阻塞,应采用明确的操作顺序。
通道:传递所有权,也要设计关闭
std::sync::mpsc 是阻塞接口,多生产者、单消费者。channel 提供无界排队语义,sync_channel 提供有界缓冲,容量零可用于会合。异步任务中等待应使用合适的异步通道,避免阻塞 worker。
发送最后一条消息不等于关闭。所有 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");
}
容量零的 send 会与接收操作会合,但接收之后仍有处理工作。若需要知道业务完成,必须返回确认或结果;有缓冲通道的 send 更不能当成处理成功。SendError 交回未能发送的 T,让调用方可以保存、重路由或报告,避免所有权在失败路径不明不白地丢失。sync_channel
有界通道也可能参与死锁:如果发送者在 send 等待空位时持有消费者处理消息所需的锁,消费者无法释放队列压力。同步设计必须一起查看锁与通道依赖,不能只检查每把锁的获取顺序。
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 的线程保存次数。一个 Condvar 应按接口要求与同一把关联锁配合,状态检查与修改必须遵循共同协议。
把“以后不会再有结果”也放进谓词
只有 ready 标志的等待程序,如果生产者提前失败,消费者可能永远等不到 true。下面用三个状态区分等待、完成和关闭;关闭本身也要通知等待者。
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);
}
这是单生产者、单消费者、一次交付的局部模型,不是通用 channel。多等待者的关闭通常需要 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());
}
这里用零预算验证未满足条件的超时分支,不依赖线程调度速度。真实超时也不是精确的返回时刻保证:等待线程还要重新获取锁并获得 CPU。最终是谓词决定结果是否可用,而不是单凭“收到通知”或“曾经到了截止时间”。Condvar 超时
原子变量:原子性不自动发布其他数据
Relaxed 保证该原子操作本身,不建立所需的跨对象发布关系。Acquire 只有观察到相应 Release 写入或其适用释放序列时,才与其建立同步;仅在两个线程里分别写上 Acquire/Release 关键词不够。
下面用两个原子变量展示一次发布,payload 本身也用原子类型,以避免教学代码包含未同步的普通内存访问:
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 写入因此先于后续读取。示例只有一次发布;反复复用同一标志需要代际或其他协议,不能简单 reset 后就假定没有竞争。
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 保证两边在写入前都已读取,因此丢失更新是确定的,不靠重复运行碰运气。第二段 fetch_add 将递增作为一个读改写原子操作;这里计数不用于发布其他数据,且最终观察在作用域线程完成之后,因此 Relaxed 足够表达所需的计数原子性。fetch_add 的整数溢出按回绕处理;如果计数不能回绕,应使用带检查的更新协议。AtomicUsize
Barrier 只适合参与者数量和阶段清楚的场景。某个参与者在抵达前提前返回或 panic,其他线程可能一直等,Barrier 本身不提供业务关闭恢复。它也不是互斥锁:通过屏障后,各线程仍可同时继续。
每次操作都原子,为何仍丢一次更新?
分离模式下,每个读写本身都是 SeqCst 原子操作,却仍允许两个线程先读到同一个旧值。安排一次这样的交错,再换成 fetch_add:修复来自把递增变成单个读改写操作,而不是简单提高内存序。这里的计数不承担发布其他数据的职责。
正在呈现知识画面
每次操作都原子,为何仍丢一次更新?load 加 store 是两步;两个线程都读到 0 后各写 1,最终仍是 1。
{"id":"rust-atomic-lost-update","title":"每次操作都原子,为何仍丢一次更新?","summary":"load 加 store 是两步;两个线程都读到 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;更强内存序也不会把两步合并。","heading":"每次操作都原子,为何仍丢一次更新?","history":"执行历史","intro":"自由安排两个线程的读取和写入,或改用一次 fetch_add。","local":"线程局部读取","memory":"共享计数","mode":"递增方式","reset":"重置","rmw":"fetch_add","scope":"所有分离 load/store 都按 SeqCst;fetch_add 对此独立计数使用 Relaxed。每线程只递增一次。","scopeLabel":"模型范围与约定","split":"load → store"}}
Acquire 到底读到了哪次写入?
“用了 Acquire”还不够,必须追踪它读到哪个写入。这个单次发布例子把同步边单独画出来:读到 false 时没有进入业务分支,读到 true 却缺少 Release 或 Acquire 时没有本例所需保证。payload 使用原子类型,使无同步的对照仍是可讨论的定义内行为。
正在呈现知识画面
Acquire 到底读到了哪次写入?只有 Acquire 观察到对应 Release 写入时,才建立此处 payload 的发布关系。
{"id":"rust-release-acquire-edge","title":"Acquire 到底读到了哪次写入?","summary":"只有 Acquire 观察到对应 Release 写入时,才建立此处 payload 的发布关系。","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;把资源独占交给一个执行者,使用通道。
自旋示例用于展示顺序,不是通用等待策略。长等待应使用合适的阻塞或异步通知机制。验证并发程序除正常输出外,还要检查提前退出、关闭、错误、任务回收与锁依赖。
让停止条件成为协议的一部分
| 等待点 | 正常推进条件 | 必须设计的停止条件 |
| 接收通道 | 收到工作 | 所有发送端关闭、显式终止消息或截止时间 |
| Condvar | 受锁保护的谓词改变 | 关闭/失败也进入谓词,并通知等待者 |
| Barrier | 足够参与者抵达 | 参与者不会在中途无协调地退出 |
| join | 目标线程结束 | 工作循环能观察停止状态,阻塞等待有退出路径 |
一个 AtomicBool 停止标志不能唤醒正在 recv 或 Condvar 上阻塞的线程。除了写标志,还需要关闭通道、发通知或使用带超时的等待,使线程有机会重新检查停止条件。join 则只是等待退出,不会替线程发出取消请求。
排查卡住时先画出等待关系:谁等待谁提供数据或释放资源,谁又持有对方所需资源。重复 sleep、增加线程数或换更强内存序,通常不能修复这种协议层循环依赖。