Rust 中的线程安全可变非拥有指针?

2024-01-11

我正在尝试并行化我拥有的算法。这是我如何用 C++ 编写它的草图:

void thread_func(std::vector<int>& results, int threadid) {
   results[threadid] = threadid;
}

std::vector<int> foo() {
  std::vector<int> results(4);

  for(int i = 0; i < 4; i++)
  {
     spawn_thread(thread_func, results, i);
  }

  join_threads();

  return results;
}

这里的要点是,每个线程都有一个对其不拥有的共享、可变对象的引用。这在 Rust 中似乎很难做到。我应该尝试将它拼凑在一起(我在这里猜测)Mutex, Cell and &mut,或者我应该遵循更好的模式?


正确的方法是使用Arc<Mutex<...>>或者,例如,Arc<RWLock<...>>. Arc是一个基于共享所有权的并发安全指针,指向不可变数据,并且Mutex/RWLock引入同步的内部可变性。然后你的代码将如下所示:

use std::sync::{Arc, Mutex};
use std::thread;

fn thread_func(results: Arc<Mutex<Vec<i32>>>, thread_id: i32) {
    let mut results = results.lock().unwrap();
    results[thread_id as usize] = thread_id;
}

fn foo() -> Arc<Mutex<Vec<i32>>> {
    let results = Arc::new(Mutex::new(vec![0; 4]));

    let guards: Vec<_> = (0..4).map(|i| {
        let results = results.clone();
        thread::spawn(move || thread_func(results, i))
    }).collect();

    for guard in guards {
        guard.join();
    }

    results
}

不幸的是,这需要您返回Arc<Mutex<Vec<i32>>>来自函数,因为无法“解开”该值。另一种方法是在返回之前克隆载体。

但是,使用像这样的板条箱作用域线程池 https://crates.io/crates/scoped_threadpool(其方法最近才变得合理;类似的东西可能会进入标准库,而不是现在已弃用的thread::scoped() http://doc.rust-lang.org/std/thread/fn.scoped.html函数,这是不安全的)它可以以更好的方式完成:

extern crate scoped_threadpool;

use scoped_threadpool::Pool;

fn thread_func(result: &mut i32, thread_id: i32) {
    *result = thread_id;
}

fn foo() -> Vec<i32> {
    let results = vec![0; 4];
    let mut pool = Pool::new(4);

    pool.scoped(|scope| {
        for (i, e) in results.iter_mut().enumerate() {
            scope.execute(move || thread_func(e, i as i32));
        }
    });

    results
}

If your thread_func需要访问整个向量,但是,如果没有同步,您就无法逃脱,因此您需要一个Mutex,你仍然会遇到展开问题:

extern crate scoped_threadpool;

use std::sync::Mutex;

use scoped_threadpool::Pool;

fn thread_func(results: &Mutex<Vec<u32>>, thread_id: i32) {
    let mut results = results.lock().unwrap();
    result[thread_id as usize] = thread_id;
}

fn foo() -> Vec<i32> {
    let results = Mutex::new(vec![0; 4]);
    let mut pool = Pool::new(4);

    pool.scoped(|scope| {
        for i in 0..4 {
            scope.execute(move || thread_func(&results, i));
        }
    });

    results.lock().unwrap().clone()
}

但至少你不需要任何Arc在这里。还execute()方法是unsafe如果您使用稳定编译器,因为它没有相应的修复程序来保证其安全。根据其构建脚本,它在高于 1.4.0 的所有编译器版本上都是安全的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Rust 中的线程安全可变非拥有指针? 的相关文章

随机推荐