如何在不使用互斥锁的情况下从任意索引处的多个线程写入可变切片?
我有两个从另一个方法传递过来的切片:
fn example<T>(a1: &[T], a2: &mut [T]) {}
我想a1使用多个线程进行处理,然后a2使用仅在每个线程执行时才知道的完全任意索引写入。我的算法保证索引是互斥的,所以没有数据竞争。
借用检查器不喜欢在线程之间共享可变引用,因为它不知道我们的算法所做的保证。我也收到lifetime 'static required rustc (E0621)错误。
那么如何在 Rust 中做到这一点呢?
回答
- 如何将堆栈变量的引用传递给线程?
- 同时可变访问保证不相交的大向量的任意索引
- 如何将不相交的切片从向量传递到不同的线程?
- 如何在分区数组上运行并行计算线程?
- 如何同时获取对两个数组元素的可变引用?
不要回答我的问题。
第一个问题的答案解决了范围问题,而不是访问任意互不相交的索引的问题。第二个问题的答案表明,as_slice_of_cells但由于上述原因,即任意访问,这在这里不起作用。第三个问题的答案同样暗示,as_slice_of_cells但同样,数组可以分成不相交的部分的假设在这里无法实现。第四个问题再次询问对数组进行分区,我们在这里不能这样做。这同样适用于第五个问题。
范围界定问题的一个答案(/sf/answers/4515197711/)实际上试图解决这个问题,但它并不建议使用横梁,并且建议的替代方案比此处的最佳答案更不安全。
回答
在尝试实现算法时,您遇到了两个不同的问题:
'static跨线程共享非引用是不可能的std::thread::spawn。- 如果您可以通过将切片拆分为多个较小的切片并将每个拆分的切片专门分配给每个线程,则可以安全地在没有同步的情况下写入切片中互不相交的索引。
通过使用crossbeam::scope生成线程而不是std::thread::spawn. 然而,后一个问题需要一个不安全的解决方案。但是,由于您知道索引是相互不相交的,因此在实践中不存在数据争用,您可以使用它UnsafeCell向编译器断言不存在数据争用。要对切片执行此操作,您可以使用以下实用程序:
use std::cell::UnsafeCell;
#[derive(Copy, Clone)]
pub struct UnsafeSlice<'a, T> {
slice: &'a [UnsafeCell<T>],
}
unsafe impl<'a, T: Send + Sync> Send for UnsafeSlice<'a, T> {}
unsafe impl<'a, T: Send + Sync> Sync for UnsafeSlice<'a, T> {}
impl<'a, T> UnsafeSlice<'a, T> {
pub fn new(slice: &'a mut [T]) -> Self {
let ptr = slice as *mut [T] as *const [UnsafeCell<T>];
Self {
slice: unsafe { &*ptr },
}
}
/// SAFETY: It is UB if two threads write to the same index without
/// synchronization.
pub unsafe fn write(&self, i: usize, value: T) {
let ptr = self.slice[i].get();
*ptr = value;
}
}
此实用程序允许您将独占切片&mut [T]转换为可以共享但仍用于突变的切片。当然,这意味着写入它可能会导致数据竞争,如果多个线程在没有同步的情况下写入同一个索引。因此,该write方法是不安全的,如果违反此假设将导致 UB
该UnsafeSlice实用程序仍将保证您在使用它时没有释放后使用或越界错误。仅使用 关闭对竞争条件的验证UnsafeSlice。
要查看构造函数中的转换是否合理,请查看Cell::from_mut和Cell::as_slice_of_cells方法中的安全注释。