如何在Rust中利用多线程并发修改Arc包裹数组的不同元素?
如何在Rust中利用多线程并发修改Arc包裹数组的不同元素?
首先得帮你找出原代码里的两个核心问题:
Arc::get_mut的使用误区:Arc::get_mut只有当当前Arc是数据的唯一所有者时才会返回Some,你在循环里多次clone了Arc,每个线程都持有一个Arc实例,此时数据有多个所有者,get_mut自然返回None,unwrap就触发panic了。- 数组索引越界:你的循环范围是
1..=2,但数组[[0;2];2]的有效索引是0和1,i=2会直接触发越界错误,这也是个隐藏的风险点。
下面给你三种可行的解决方案,你可以根据自己的需求选择:
方案一:用Arc<UnsafeCell>结合unsafe(高性能,需保证线程安全)
这种方式适合追求极致性能的场景,因为我们明确每个线程只修改互不重叠的行,所以unsafe操作是安全的:
#![allow(dead_code)] use std::sync::Arc; use std::cell::UnsafeCell; use std::thread; use std::time::Instant; fn main() { // 用UnsafeCell包裹数组,允许内部可变访问 let arr = Arc::new(UnsafeCell::new([[0; 2]; 2])); let t1 = Instant::now(); let mut handles = Vec::new(); // 修正索引范围为0..2 for i in 0..2 { let arr_clone = Arc::clone(&arr); let handle = thread::spawn(move || { // 获取原始指针,因为我们保证线程只修改独立行,所以unsafe是安全的 let ptr = arr_clone.get(); unsafe { (*ptr)[i] = [10, 100]; } }); handles.push(handle); } // 等待所有线程完成 for h in handles { h.join().unwrap(); } let tot1 = t1.elapsed(); // 安全读取最终结果(所有线程已完成,无数据竞争) let final_arr = unsafe { &*arr.get() }; println!("arr = {:?}", final_arr); println!("time consumed: {:?}", tot1); }
方案二:用Mutex包裹每行(完全安全,无unsafe)
这种方式不需要写unsafe代码,每个行单独用Mutex保护,因为修改的是不同行,几乎不会有锁竞争,性能损失可以忽略:
#![allow(dead_code)] use std::sync::{Arc, Mutex}; use std::thread; use std::time::Instant; fn main() { // 数组的每一行都用Mutex包裹 let arr = Arc::new([Mutex::new([0; 2]), Mutex::new([0; 2])]); let t1 = Instant::now(); let mut handles = Vec::new(); for i in 0..2 { let arr_clone = Arc::clone(&arr); let handle = thread::spawn(move || { // 获取当前行的锁并修改 let mut row = arr_clone[i].lock().unwrap(); *row = [10, 100]; }); handles.push(handle); } for h in handles { h.join().unwrap(); } let tot1 = t1.elapsed(); // 收集每一行的结果 let final_arr: [[i32;2];2] = [ *arr[0].lock().unwrap(), *arr[1].lock().unwrap(), ]; println!("arr = {:?}", final_arr); println!("time consumed: {:?}", tot1); }
方案三:使用作用域线程(最简洁,无需Arc)
如果你的线程不需要脱离当前函数的作用域,推荐用crossbeam库的作用域线程,代码更简洁,还能避免Arc的克隆开销:
首先需要在Cargo.toml中添加依赖:
[dependencies] crossbeam = "0.8"
然后编写代码:
#![allow(dead_code)] use crossbeam::thread; use std::time::Instant; fn main() { let mut arr = [[0; 2]; 2]; let t1 = Instant::now(); // 作用域线程可以直接借用数组的可变引用,无需Arc thread::scope(|s| { for i in 0..2 { s.spawn(move |_| { arr[i] = [10, 100]; }); } }).unwrap(); let tot1 = t1.elapsed(); println!("arr = {:?}", arr); println!("time consumed: {:?}", tot1); }
方案选择建议
- 追求极致性能且能保证线程访问安全:选方案一;
- 不想写unsafe代码,优先安全:选方案二;
- 线程不脱离当前函数作用域:选方案三,代码最简洁高效。
备注:内容来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

