简体   繁体   English

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

[英]Thread-safe mutable non-owning pointer in Rust?

I'm trying to parallelize an algorithm I have. 我正在尝试并行化我的算法。 This is a sketch of how I would write it in C++: 这是我将如何用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;
}

The point here is that each thread has a reference to a shared, mutable object that it does not own. 这里的要点是每个线程都有一个它不拥有的共享可变对象的引用。 It seems like this is difficult to do in Rust. 在Rust中,这似乎很难做到。 Should I try to cobble it together in terms of (and I'm guessing here) Mutex , Cell and &mut , or is there a better pattern I should follow? 我是否应该尝试将它拼凑在一起(我猜这里) MutexCell&mut ,或者我应该遵循更好的模式?

The proper way is to use Arc<Mutex<...>> or, for example, Arc<RWLock<...>> . 正确的方法是使用Arc<Mutex<...>>或者,例如, Arc<RWLock<...>> Arc is a shared ownership-based concurrency-safe pointer to immutable data, and Mutex / RWLock introduce synchronized internal mutability. Arc是一个共享的基于所有权的并发安全指针,指向不可变数据, Mutex / RWLock引入了同步内部可变性。 Your code then would look like this: 您的代码将如下所示:

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
}

This unfortunately requires you to return Arc<Mutex<Vec<i32>>> from the function because there is no way to "unwrap" the value. 遗憾的是,这要求您从函数返回Arc<Mutex<Vec<i32>>> ,因为无法“解包”该值。 An alternative is to clone the vector before returning. 另一种方法是在返回之前克隆向量。

However, using a crate like scoped_threadpool (whose approach could only be recently made sound; something like it will probably make into the standard library instead of the now deprecated thread::scoped() function, which is unsafe) it can be done in a much nicer way: 但是,使用像scoped_threadpool那样的 (其方法最近才能发出声音;类似于它的东西可能会进入标准库而不是现在已弃用的thread::scoped()函数,这是不安全的)它可以在一个更好的方式:

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 needs to access the whole vector, however, you can't get away without synchronization, so you would need a Mutex , and you would still get the unwrapping problem: 但是,如果你的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()
}

But at least you don't need any Arc s here. 但至少你不需要任何Arc Also execute() method is unsafe if you use stable compiler because it does not have a corresponding fix to make it safe. 如果使用稳定的编译器, execute()方法也是unsafe ,因为它没有相应的修复来使其安全。 It is safe on all compiler versions greater than 1.4.0, according to its build script. 根据其构建脚本,它在大于1.4.0的所有编译器版本上都是安全的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM