Rust - 在线程之间不安全地共享没有互斥体的可变数据

ube*_*ben 7 multithreading mutex unsafe rust

如何在 Rust 中实现老式多线程(无包装互斥)?为什么这是未定义的行为?

我必须建立一个高度并发的物理模拟。我应该用 C 语言来做,但我选择了 Rust(我确实需要更高级别的功能)。

通过使用 Rust,我应该选择线程之间的安全通信,但是,我必须使用线程之间共享的可变缓冲区。(实际上,我必须实施不同的技术并对它们进行基准测试)

第一种方法

  1. 用于Arc<Data>共享非可变状态。

  2. 需要时用于transmute推广。&&mut

这很简单,但即使使用块,编译器也会阻止其编译unsafe。这是因为编译器可以应用优化,知道这些数据应该是不可变的(可能被缓存并且从未更新,不是这方面的专家)。

这种优化可以通过Cell包装器和其他方法来停止。

第二种方法

  1. 使用Arc<UnsafeCell<Data>>

  2. 然后data.get()访问数据。

这也不能编译。原因是UnsafeCell不是Send。解决方案是使用SyncUnsafeCell但目前不稳定的版本(1.66),程序将在只有稳定版本的机器上编译并投入生产。

第三种方法

  1. 使用Arc<Mutex<Data>>

  2. 在每个线程的开头:

    • 锁定互斥体。

    • *mut通过强制 a 来保留 a &mut

    • 释放互斥体。

  3. *mut需要时使用

我还没有尝试过这个,但即使它可以编译,它是否安全(不谈论数据竞争)SyncUnsafeCell

PS:同时变异的值只是f32,绝对没有内存分配或任何复杂的操作同时发生。最坏的情况,我已经打乱了一些f32

Fin*_*nis 7

免责声明:可能有很多方法可以解决这个问题,这只是其中之一,基于@Caesar的想法。

这篇文章的两个要点:

  • 您可以在线程之间AtomicU32共享,f32而不会造成任何性能损失(给定一个已经是原子的架构u32
  • 您可以使用std::thread::scope来避免 的开销Arc
use std::{
    fmt::Debug,
    ops::Range,
    sync::atomic::{AtomicU32, Ordering},
};

struct AtomicF32(AtomicU32);
impl AtomicF32 {
    pub fn new(val: f32) -> Self {
        Self(AtomicU32::new(val.to_bits()))
    }
    pub fn load(&self, order: Ordering) -> f32 {
        f32::from_bits(self.0.load(order))
    }
    pub fn store(&self, val: f32, order: Ordering) {
        self.0.store(val.to_bits(), order)
    }
}
impl Debug for AtomicF32 {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.load(Ordering::Relaxed).fmt(f)
    }
}

fn perform_action(data: &Vec<AtomicF32>, range: Range<usize>) {
    for value_raw in &data[range] {
        let mut value = value_raw.load(Ordering::Relaxed);
        value *= 2.5;
        value_raw.store(value, Ordering::Relaxed);
    }
}

fn main() {
    let data = (1..=10)
        .map(|v| AtomicF32::new(v as f32))
        .collect::<Vec<_>>();

    println!("Before: {:?}", data);

    std::thread::scope(|s| {
        s.spawn(|| perform_action(&data, 0..5));
        s.spawn(|| perform_action(&data, 5..10));
    });

    println!("After: {:?}", data);
}
Run Code Online (Sandbox Code Playgroud)
Before: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
After: [2.5, 5.0, 7.5, 10.0, 12.5, 15.0, 17.5, 20.0, 22.5, 25.0]
Run Code Online (Sandbox Code Playgroud)

为了演示它的轻量级,下面是它的编译结果

Before: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
After: [2.5, 5.0, 7.5, 10.0, 12.5, 15.0, 17.5, 20.0, 22.5, 25.0]
Run Code Online (Sandbox Code Playgroud)
.LCPI0_0:
        .long   0x40200000
example::perform_action:
        movss   xmm0, dword ptr [rdi]
        mulss   xmm0, dword ptr [rip + .LCPI0_0]
        movss   dword ptr [rdi], xmm0
        ret
Run Code Online (Sandbox Code Playgroud)

请注意,虽然这包含零个未定义行为,但程​​序员仍然有责任避免读取-修改-写入竞争条件。