我正在开发一个并发 Rust 应用程序,我需要多个线程来访问和修改共享数据结构的不同部分。据我所知,Rust 强制执行严格的借用规则来防止数据竞争,这会使并发上下文中的多个可变引用变得复杂。但是,我想优化我的设计以最小化锁定开销并最大化并发性。
假设我有一个存储多个项目的 Vec,并且我希望每个线程处理向量中的不同元素,而不会阻塞其他线程。我考虑过使用 Mutex 或 RwLock,但这些会增加 global 锁定开销,出于性能原因,我希望避免这种情况。我正在寻找更精细的并发控制,允许线程独立修改不同的元素。
这是我当前设置的简化版本:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let data = Arc::new(Mutex::new(vec![1, 2, 3, 4]));
let mut handles = vec![];
for i in 0..4 {
let data = Arc::clone(&data);
let handle = thread::spawn(move || {
let mut data = data.lock().unwrap();
data[i] += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("{:?}", *data.lock().unwrap());
}
这段代码可以工作,但是在整个 Vec 周围使用互斥锁意味着一次只有一个线程可以修改任何元素,这可能会导致性能瓶颈。
P.S. 我读过有关使用 Atomic 类型、Crossbeam 甚至将结构拆分为独立块等选项,但我不确定在 Rust 中优化并发访问的最佳方法是什么。
P.P.S. 由于我对这门语言本身相当陌生,我的一些前提甚至可能不正确,所以请持保留态度。
split_at_mut
切片方法在这里派上用场。使用它,您可以将任何取消引用切片的内容(数组、Vec
等)划分为不相交的段,这两个段都可以同时发生变化。
为此,您不能使用
Arc
与线程共享数据;您需要直接共享参考。为了让 that 工作,您需要使用 作用域线程。
这是一个与您的代码执行相同操作的示例,但不需要锁定或
Arc
:
use std::thread;
fn main() {
let mut data = vec![1, 2, 3, 4];
let mut remaining_data = &mut data[..];
thread::scope(|s| {
while !remaining_data.is_empty() {
// Split off the first element into its own slice.
let thread_data;
(thread_data, remaining_data) = remaining_data.split_at_mut(1);
// Take a reference to the only element to keep the thread code
// simpler.
let thread_data = &mut thread_data[0];
s.spawn(|| {
*thread_data += 1;
});
}
});
assert_eq!(&[2, 3, 4, 5], &data[..]);
}