如何将不相交的切片从向量传递到不同的线程?

时间:2015-11-20 02:44:58

标签: rust

我是Rust的新手,并努力处理Rust中的所有包装器类型。我试图编写在语义上等于以下C代码的代码。代码尝试创建一个用于簿记的大表,但是会分割大表,以便每个线程只访问该表的本地小片。除非其他线程退出并且不再访问自己的切片,否则不会访问大表。

#include <stdio.h>
#include <pthread.h>

void* write_slice(void* arg) {
    int* slice = (int*) arg;
    int i;
    for (i = 0; i < 10; i++)
        slice[i] = i;

    return NULL;
}

int main()
{
    int* table = (int*) malloc(100 * sizeof(int));
    int* slice[10];
    int i;
    for (i = 0; i < 10; i++) {
      slice[i] = table + i * 10;
    }

    // create pthread for each slice
    pthread_t p[10];
    for (i = 0; i < 10; i++)
        pthread_create(&p[i], NULL, write_slice, slice[i]);

    for (i = 0; i < 10; i++)
        pthread_join(p[i], NULL);

    for (i = 0; i < 100; i++)
        printf("%d,", table[i]);
}

如何使用Rust的类型和所有权来实现这一目标?

1 个答案:

答案 0 :(得分:13)

让我们从代码开始:

// cargo-deps: crossbeam="0.1.6"
extern crate crossbeam;

const CHUNKS: usize = 10;
const CHUNK_SIZE: usize = 10;

fn main() {
    let mut table = [0; CHUNKS * CHUNK_SIZE];

    // Scoped threads allow the compiler to prove that no threads will outlive
    // table (which would be bad).
    crossbeam::scope(|scope| {
        // Chop `table` into disjoint sub-slices.
        for slice in table.chunks_mut(CHUNK_SIZE) {
            // Spawn a thread operating on that subslice.
            scope.spawn(move || write_slice(slice));
        }
        // `crossbeam::scope` ensures that *all* spawned threads join before
        // returning control back from this closure.
    });

    // At this point, all threads have joined, and we have exclusive access to
    // `table` again.  Huzzah for 100% safe multi-threaded stack mutation!
    println!("{:?}", &table[..]);
}

fn write_slice(slice: &mut [i32]) {
    for (i, e) in slice.iter_mut().enumerate() {
        *e = i as i32;
    }
}

需要注意的是,这需要crossbeam箱。 Rust 使用有一个类似的“范围”构造,但是在1.0之前发现了正确的健全漏洞,所以它被弃用了,没有时间来替换它。 crossbeam基本上是替代品。

Rust允许你在这里做的是表达这样的想法,无论代码如何,在crossbeam::scoped的调用中创建的线程的 none 都将在该范围内存活。因此,从外部借用的任何东西都会比线程更长寿。因此,线程可以自由地访问那些借用,而不必担心诸如一个线程超出堆栈帧table定义并在堆栈上乱写的东西。

所以这应该与C代码或多或少相同,尽管没有唠叨担心你可能错过了什么。 :)

最后,使用scoped_threadpool代替同样的事情。唯一真正的实际区别是,这允许我们控制使用的线程数。

// cargo-deps: scoped_threadpool="0.1.6"
extern crate scoped_threadpool;

const CHUNKS: usize = 10;
const CHUNK_SIZE: usize = 10;

fn main() {
    let mut table = [0; CHUNKS * CHUNK_SIZE];

    let mut pool = scoped_threadpool::Pool::new(CHUNKS as u32);

    pool.scoped(|scope| {
        for slice in table.chunks_mut(CHUNK_SIZE) {
            scope.execute(move || write_slice(slice));
        }
    });

    println!("{:?}", &table[..]);
}

fn write_slice(slice: &mut [i32]) {
    for (i, e) in slice.iter_mut().enumerate() {
        *e = i as i32;
    }
}