如何在Rust中不安全定义可跨线程共享并修改的结构体?
在Rust中手动实现自定义同步的跨线程可变结构体
问题说明
需要不安全地实现一个可在多线程间共享并修改的结构体,不依赖Mutex或RwLock,自行控制线程安全逻辑;出于性能考虑,在确定处于非临界区时要跳过锁检查。仅使用Arc会遇到可变借用错误:cannot borrow data in an Arc as mutable,原因是Arc未实现DerefMut trait。
安全实现示例(基于Mutex)
struct Foo { bar: usize, } impl Foo { pub fn set_bar(&mut self, a: usize) { self.bar = a; } } fn main() { let mut foo = Foo { bar: 32 }; foo.bar = 33; let foo_arc = std::sync::Arc::new(std::sync::Mutex::new(foo)); let foo_arc_2 = std::sync::Arc::clone(&foo_arc); let handle = std::thread::spawn(move || { foo_arc_2.lock().unwrap().set_bar(32); }); foo_arc.lock().unwrap().set_bar(31); handle.join().unwrap(); }
自行尝试的不安全代码(存在问题)
struct Foo { bar: usize, // 自定义锁占位 // lock: std::sync::Mutex<usize>, } unsafe impl Sync for Foo {} impl Foo { pub fn set_bar(&mut self, a: usize) { self.bar = a; } } fn main() { let mut foo = Foo { bar: 32 }; foo.bar = 33; let foo_arc = std::sync::Arc::new(foo); let foo_arc_2 = std::sync::Arc::clone(&foo_arc); let handle = std::thread::spawn(move || { foo_arc_2.set_bar(32); }); foo_arc.set_bar(31); handle.join().unwrap(); }
同时也考虑过是否可以不用Arc,改用更底层的内存操作方式。
解决方案
要实现需求,需要结合Arc提供共享所有权、UnsafeCell提供内部可变性,再配合自定义的同步原语(比如原子变量),手动保证线程安全:
1. 核心逻辑说明
Arc负责多线程共享所有权,但内部数据默认不可变,必须用UnsafeCell绕开这个限制,允许内部可变访问。- 用原子变量实现自定义同步机制(比如自旋锁),在临界区强制互斥访问,非临界区可直接调用unsafe方法跳过锁检查。
- 必须严格保证:只有在同步机制保护下才进行可变操作,否则会触发数据竞争和未定义行为。
2. 完整实现示例
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, UnsafeCell}; struct Foo { bar: UnsafeCell<usize>, // 自定义自旋锁:false=未锁定,true=已锁定 lock: AtomicBool, } // 手动实现Sync:我们通过原子锁保证了线程安全 unsafe impl Sync for Foo {} // Send自动推导,因为所有成员都实现Send impl Foo { pub fn new(initial_bar: usize) -> Self { Foo { bar: UnsafeCell::new(initial_bar), lock: AtomicBool::new(false), } } // 带锁的可变操作:用于临界区 pub fn set_bar(&self, a: usize) { // 自旋等待锁释放 while self.lock.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {} // 进入临界区,安全访问UnsafeCell内部数据 unsafe { *self.bar.get() = a; } // 释放锁,保证写入操作对其他线程可见 self.lock.store(false, Ordering::Release); } // 无锁可变操作:仅当确定无其他线程访问时调用(unsafe) pub unsafe fn set_bar_unchecked(&self, a: usize) { *self.bar.get() = a; } // 读取操作:保证读取到最新值 pub fn get_bar(&self) -> usize { // 等待锁释放,确保读取前所有写入完成 while self.lock.load(Ordering::Relaxed) {} unsafe { *self.bar.get() } } } fn main() { let foo = Foo::new(32); let foo_arc = Arc::new(foo); let foo_arc_2 = Arc::clone(&foo_arc); let handle = std::thread::spawn(move || { foo_arc_2.set_bar(32); // 若确定此时无其他线程操作,可调用unsafe方法 // unsafe { foo_arc_2.set_bar_unchecked(33); } }); foo_arc.set_bar(31); handle.join().unwrap(); println!("bar: {}", foo_arc.get_bar()); }
3. 不用Arc的底层指针实现
如果不想依赖Arc,可以手动管理堆内存,用原始指针在线程间传递,风险更高但更底层:
use std::sync::atomic::{AtomicBool, Ordering}; use std::thread; use std::alloc::{alloc, dealloc, Layout}; struct Foo { bar: usize, lock: AtomicBool, } unsafe impl Sync for Foo {} impl Foo { pub fn set_bar(&mut self, a: usize) { while self.lock.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {} self.bar = a; self.lock.store(false, Ordering::Release); } } fn main() { // 手动分配堆内存 let layout = Layout::new::<Foo>(); let foo_ptr = unsafe { alloc(layout) as *mut Foo }; unsafe { (*foo_ptr) = Foo { bar: 32, lock: AtomicBool::new(false) }; } // 传递原始指针到子线程 let foo_ptr_2 = foo_ptr; let handle = thread::spawn(move || { unsafe { (*foo_ptr_2).set_bar(32); } }); unsafe { (*foo_ptr).set_bar(31); } handle.join().unwrap(); // 手动释放内存 unsafe { dealloc(foo_ptr as *mut u8, layout); } }
关键注意事项
- 所有可变操作必须在自定义同步机制的保护下执行,否则会引发数据竞争,导致未定义行为。
UnsafeCell是Rust中唯一合法的内部可变实现方式,直接跨线程使用&mut会违反Rust的借用规则。- 原子操作的内存顺序(
Ordering)必须正确选择:Acquire保证后续读取能看到之前的写入,Release保证之前的写入对其他线程可见,错误的内存顺序会导致可见性问题。
内容的提问来源于stack exchange,提问作者javier
相关产品推荐
相关产品推荐

