如何使用AtomicCell::compare_exchange更新结构体指定字段?
问题描述
- 需求场景:一个线程每10ms更新f64类型的角度数据,另一个线程每1秒仅在数据有更新时打印该角度值。
- 技术选型:使用crossbeam crate的
AtomicCell<Struct>实现线程间数据共享。 - 现存问题:
- 需要原子性执行两个操作:仅当结构体的
is_updated字段为true时,将其重置为false,同时获取对应的角度值。 - 当前代码通过两次独立原子操作实现,存在竞态条件,不具备完全原子性。
- 因存在无需关注
is_updated状态、直接读取角度值的需求,无法使用AtomicF64 crate。
- 需要原子性执行两个操作:仅当结构体的
原始尝试代码
use crossbeam::atomic::AtomicCell; #[derive(Default, Copy, Clone)] struct AnglePosition { is_updated: bool, angle: f64, } fn main() { let angle_pos = AtomicCell::new(AnglePosition { is_updated: true, angle: 56.789, }); // 期望用AtomicCell::compare_exchange实现:仅当is_updated为true时将其设为false,同时保留angle值 // 以下操作非完全原子,因为是两次独立原子操作 let updated_angle_pos = angle_pos.load(); if updated_angle_pos.is_updated { angle_pos.store(AnglePosition { is_updated: false, angle: updated_angle_pos.angle, }); println!("angle position data updated"); } let updated_angle_pos = angle_pos.load(); if !updated_angle_pos.is_updated { println!("angle position data not updated after last read"); } }
解决方案
要实现完全原子的状态检查与更新,需利用AtomicCell::compare_exchange的原子特性:基于当前加载的结构体实例构造预期值和目标值,通过单次原子操作完成匹配检查与值替换。
核心实现思路
- 加载当前的
AnglePosition实例作为预期匹配值。 - 若预期值的
is_updated为true,构造新值:保留原角度值,将is_updated设为false。 - 调用
compare_exchange:如果当前内存中的值与预期值完全匹配,则原子替换为新值;若不匹配(其他线程已修改数据),则重新加载当前值重试。
完整可运行代码
use crossbeam::atomic::AtomicCell; use std::thread; use std::time::Duration; #[derive(Default, Copy, Clone, PartialEq)] struct AnglePosition { is_updated: bool, angle: f64, } fn main() { let angle_pos = AtomicCell::new(AnglePosition::default()); // 角度更新线程:每10ms更新角度并标记为已更新 thread::spawn(move || { let mut angle = 0.0; loop { angle_pos.store(AnglePosition { is_updated: true, angle, }); angle += 1.0; thread::sleep(Duration::from_millis(10)); } }); // 打印线程:每1秒检查并打印更新后的角度 thread::spawn(move || { loop { loop { let current = angle_pos.load(); if !current.is_updated { break; } // 构造目标值:保留原角度,重置更新标记 let new_val = AnglePosition { is_updated: false, angle: current.angle, }; // 原子交换操作:仅当当前值与预期值一致时执行替换 match angle_pos.compare_exchange(current, new_val) { Ok(_) => { println!("更新后的角度: {}", current.angle); break; } Err(_) => { // 其他线程修改了数据,重新加载重试 continue; } } } thread::sleep(Duration::from_secs(1)); } }); // 保持主线程存活 loop { thread::sleep(Duration::from_secs(10)); } }
关键细节说明
- 原子性保障:
compare_exchange是单一硬件原子操作,彻底避免了先加载再存储的竞态条件,确保"检查更新标记+重置标记"的操作不可分割。 - 重试逻辑:当
compare_exchange返回Err时,说明其他线程在加载值和尝试交换的间隙修改了数据,此时重新加载当前值并重试,确保不会遗漏任何更新。 - 直接读取支持:依然可以通过
angle_pos.load().angle直接获取最新角度值,完全满足无需关注is_updated状态的读取需求。
内容的提问来源于stack exchange,提问作者Harry
相关产品推荐
相关产品推荐

