You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从C++转Rust:并发索引数组写入及无竞态数组传递问题

Rust并发数组访问:从C++到Rust的常见问题解答

一、处理有竞态条件的并发修改场景

你提到在C++里会用锁或原子操作保护数组索引,在Rust里我们可以利用语言的安全特性,用更优雅且不易出错的方式实现。以下是两种常见方案:

1. 原子类型数组(适合简单数值操作)

如果你的数组元素是可以用原子操作处理的数值类型(比如整数),直接使用原子类型数组是最高效的选择,不需要锁:

use std::sync::atomic::{AtomicUsize, Ordering};
use rayon::prelude::*;

fn main() {
    let indices = [1, 2, 3, 4, 1, 2, 3, 2, 1, 1, 3, 2, 2];
    // 将普通数组转换为原子类型数组,每个元素都是可原子操作的
    let arr = [
        AtomicUsize::new(1),
        AtomicUsize::new(2),
        AtomicUsize::new(3),
        AtomicUsize::new(4),
        AtomicUsize::new(5),
        AtomicUsize::new(6),
        AtomicUsize::new(7),
        AtomicUsize::new(8),
        AtomicUsize::new(10),
    ];

    indices.par_iter().for_each(|&x| {
        // 使用fetch_add执行原子加法,Ordering::Relaxed足以满足这个场景的内存顺序需求
        arr[x].fetch_add(x, Ordering::Relaxed);
    });

    // 验证结果
    for (i, val) in arr.iter().enumerate() {
        println!("arr[{}] = {}", i, val.load(Ordering::Relaxed));
    }
}

2. 细粒度Mutex锁(适合复杂操作)

如果每个元素的修改逻辑比较复杂,无法用原子操作完成,可以给每个元素单独加一个Mutex,这样锁的粒度更细,减少线程竞争:

use std::sync::{Arc, Mutex};
use rayon::prelude::*;

fn main() {
    let indices = [1, 2, 3, 4, 1, 2, 3, 2, 1, 1, 3, 2, 2];
    // 用Arc包装Mutex数组,实现多线程共享
    let arr = Arc::new([
        Mutex::new(1),
        Mutex::new(2),
        Mutex::new(3),
        Mutex::new(4),
        Mutex::new(5),
        Mutex::new(6),
        Mutex::new(7),
        Mutex::new(8),
        Mutex::new(10),
    ]);

    indices.par_iter().for_each(|&x| {
        // 锁定对应索引的元素,修改后锁会自动释放(作用域结束时)
        let mut val = arr[x].lock().unwrap();
        *val += x;
        // 这里可以添加任意复杂的修改逻辑,只要在锁的保护下即可
    });

    // 打印结果
    for (i, val) in arr.iter().enumerate() {
        println!("arr[{}] = {}", i, val.lock().unwrap());
    }
}

Rust的Mutex会在作用域结束时自动释放锁,完全不用担心C++里常见的忘记解锁导致的死锁问题,安全性拉满。

二、确定无竞态时的可变数组并行访问

当你能100%保证每个数组索引只会被一个线程访问(比如你的indices数组里没有重复值),可以用UnsafeCell来绕过Rust的可变引用安全检查——但记住,unsafe的安全性需要由你自己来保证:

use std::cell::UnsafeCell;
use std::sync::Arc;
use rayon::prelude::*;

// 示例函数
fn some_function(x: &usize) -> usize {
    *x * 2
}

fn main() {
    let indices = [1, 2, 3, 4, 5, 6, 7, 8];
    let mut arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    // 用UnsafeCell包装数组,再通过Arc实现多线程共享
    let arr = Arc::new(UnsafeCell::new(arr));

    indices.par_iter().for_each(|&x| {
        // 因为我们确定没有竞态,所以这个unsafe块是安全的
        let arr_ptr = arr.get();
        unsafe {
            (*arr_ptr)[x] = some_function(&x);
        }
    });

    // 取出最终结果
    let result = unsafe { (*arr.get()).clone() };
    println!("修改后的数组:{:?}", result);
}

注意事项

  • 必须确保你的索引没有重复,否则会出现未定义行为,Rust不会帮你检查这一点;
  • 要使用并行迭代器,需要在Cargo.toml中添加rayon依赖:rayon = "1.7";
  • 尽量避免不必要的unsafe,只有当你完全确定安全时再使用。

内容的提问来源于stack exchange,提问作者Adam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 17:12:59