【问题标题】:How do I run parallel threads of computation on a partitioned array?如何在分区数组上运行并行计算线程?
【发布时间】:2015-02-19 05:39:59
【问题描述】:

我正在尝试跨线程分配数组,并让线程并行汇总数组的各个部分。我希望线程 0 对元素 0 1 2 求和,线程 1 对元素 3 4 5 求和。线程 2 对 6 和 7 求和。线程 3 对 8 和 9 求和。

我是 Rust 新手,但之前用 C/C++/Java 编写过代码。我真的把所有东西和垃圾槽都扔到了这个程序上,我希望我能得到一些指导。

对不起,我的代码很草率,但我会在它完成后清理它。请忽略所有命名不佳的变量/不一致的间距/等。

use std::io;
use std::rand;
use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc;
use std::thread::Thread;

static NTHREADS: usize = 4;
static NPROCS: usize = 10;

fn main() {
    let mut a = [0; 10]; // a: [i32; 10]
    let mut endpoint = a.len() / NTHREADS;
    let mut remElements = a.len() % NTHREADS;

    for x in 0..a.len() {
        let secret_number = (rand::random::<i32>() % 100) + 1;
        a[x] = secret_number;
        println!("{}", a[x]);
    }
    let mut b = a;
    let mut x = 0;

    check_sum(&mut a);
    // serial_sum(&mut b);

    // Channels have two endpoints: the `Sender<T>` and the `Receiver<T>`,
    // where `T` is the type of the message to be transferred
    // (type annotation is superfluous)
    let (tx, rx): (Sender<i32>, Receiver<i32>) = mpsc::channel();
    let mut scale: usize = 0;

    for id in 0..NTHREADS {
        // The sender endpoint can be copied
        let thread_tx = tx.clone();
        // Each thread will send its id via the channel

        Thread::spawn(move || {
            // The thread takes ownership over `thread_tx`
            // Each thread queues a message in the channel
            let numTougherThreads: usize = NPROCS % NTHREADS;
            let numTasksPerThread: usize = NPROCS / NTHREADS;
            let mut lsum = 0;

            if id < numTougherThreads {
                let mut q = numTasksPerThread+1;
                lsum = 0;

                while q > 0 {
                    lsum = lsum + a[scale];
                    scale+=1;
                    q = q-1;
                }
                println!("Less than numToughThreads lsum: {}", lsum);
            }
            if id >= numTougherThreads {
                let mut z = numTasksPerThread;
                lsum = 0;

                while z > 0 {
                    lsum = lsum + a[scale];
                    scale +=1;
                    z = z-1;
                }    
                println!("Greater than numToughthreads lsum: {}", lsum);
            }
            // Sending is a non-blocking operation, the thread will continue
            // immediately after sending its message
            println!("thread {} finished", id);
            thread_tx.send(lsum).unwrap();
        });
    }

    // Here, all the messages are collected
    let mut globalSum = 0;
    let mut ids = Vec::with_capacity(NTHREADS);
    for _ in 0..NTHREADS {
        // The `recv` method picks a message from the channel
        // `recv` will block the current thread if there no messages      available
        ids.push(rx.recv());
    }
    println!("Global Sum: {}", globalSum);
    // Show the order in which the messages were sent

    println!("ids: {:?}", ids);
}

fn check_sum (arr: &mut [i32]) {
    let mut sum = 0;
    let mut i = 0;
    let mut size = arr.len();
    loop {
        sum += arr[i];
        i+=1;
        if i == size { break; }
    }
    println!("CheckSum is {}", sum);
}

到目前为止,我已经做到了这么多。无法弄清楚为什么线程 0 和 1 具有相同的总和以及 2 和 3 做同样的事情:

 -5
 -49
 -32
 99
 45
 -65
 -64
 -29
 -56
 65
 CheckSum is -91
 Greater than numTough lsum: -54
 thread 2 finished
 Less than numTough lsum: -86
 thread 1 finished
 Less than numTough lsum: -86
 thread 0 finished
 Greater than numTough lsum: -54
 thread 3 finished
 Global Sum: 0
 ids: [Ok(-86), Ok(-86), Ok(-54), Ok(-54)]

我设法通过使用以下代码将其重写为使用偶数。

    while q > 0 {
        if id*s+scale == a.len() { break; }
        lsum = lsum + a[id*s+scale];
        scale +=1;
        q = q-1;
    }
    println!("Less than numToughThreads lsum: {}", lsum);
}
if id >= numTougherThreads {
    let mut z = numTasksPerThread;
    lsum = 0;
    let mut scale = 0;

    while z > 0 {
        if id*numTasksPerThread+scale == a.len() { break; }
        lsum = lsum + a[id*numTasksPerThread+scale];
        scale = scale + 1;
        z = z-1;
    }

【问题讨论】:

  • 您能描述一下您尝试实现的算法吗?预期的结果是什么?
  • 我正在尝试将数组分布在线程中,并让线程并行地汇总数组的各个部分。我希望线程 0 对元素 0 1 2 求和,线程 1 对元素 3 4 5 求和。线程 2 对 6 和 7 求和。线程 3 对 8 和 9 求和
  • 我设法让它与偶数如 20 和 100 一起工作
  • 您的“问题”现在不再包含问题。
  • 刚刚注意到您的check_sum 采用了&amp;mut [i32]。为什么mut?您不想改变该切片中的任何内容。

标签: multithreading random synchronization rust


【解决方案1】:

欢迎来到 Rust! :)

是的,起初我没有意识到每个线程都有自己的比例副本

不仅如此!它还拥有自己的 a 副本!

您尝试执行的操作可能类似于以下代码。我想您更容易看到完整的工作示例,因为您似乎是 Rust 初学者并寻求指导。我故意用Vec 替换[i32; 10],因为Vec 不是隐含的Copyable。它需要一个明确的clone();我们不能偶然复制它。请注意所有较大和较小的差异。该代码还具有更多功能(更少mut)。我评论了大部分值得注意的事情:

extern crate rand;

use std::sync::Arc;
use std::sync::mpsc;
use std::thread;

const NTHREADS: usize = 4; // I replaced `static` by `const`

// gets used for *all* the summing :)
fn sum<I: Iterator<Item=i32>>(iter: I) -> i32 {
    let mut s = 0;
    for x in iter {
        s += x;
    }
    s
}

fn main() {
    // We don't want to clone the whole vector into every closure.
    // So we wrap it in an `Arc`. This allows sharing it.
    // I also got rid of `mut` here by moving the computations into
    // the initialization.
    let a: Arc<Vec<_>> =
        Arc::new(
            (0..10)
                .map(|_| {
                    (rand::random::<i32>() % 100) + 1
                })
                .collect()
        );

    let (tx, rx) = mpsc::channel(); // types will be inferred

    { // local scope, we don't need the following variables outside
        let num_tasks_per_thread = a.len() / NTHREADS; // same here
        let num_tougher_threads = a.len() % NTHREADS; // same here
        let mut offset = 0;
        for id in 0..NTHREADS {
            let chunksize =
                if id < num_tougher_threads {
                    num_tasks_per_thread + 1
                } else {
                    num_tasks_per_thread
                };
            let my_a = a.clone();  // refers to the *same* `Vec`
            let my_tx = tx.clone();
            thread::spawn(move || {
                let end = offset + chunksize;
                let partial_sum =
                    sum( (&my_a[offset..end]).iter().cloned() );
                my_tx.send(partial_sum).unwrap();
            });
            offset += chunksize;
        }
    }

    // We can close this Sender
    drop(tx);

    // Iterator magic! Yay! global_sum does not need to be mutable
    let global_sum = sum(rx.iter());
    println!("global sum via threads    : {}", global_sum);
    println!("global sum single-threaded: {}", sum(a.iter().cloned()));
}

【讨论】:

  • 谢谢,我想输入数组的大小,以及来自用户的线程数……io还是bug吗?
  • 您所说的“io 仍然有问题吗?”是什么意思?在这种情况下?
【解决方案2】:

使用像crossbeam 这样的板条箱,您可以编写以下代码:

use crossbeam; // 0.7.3
use rand::distributions::{Distribution, Uniform}; // 0.7.3

const NTHREADS: usize = 4;

fn random_vec(length: usize) -> Vec<i32> {
    let step = Uniform::new_inclusive(1, 100);
    let mut rng = rand::thread_rng();
    step.sample_iter(&mut rng).take(length).collect()
}

fn main() {
    let numbers = random_vec(10);
    let num_tasks_per_thread = numbers.len() / NTHREADS;

    crossbeam::scope(|scope| {
        // The `collect` is important to eagerly start the threads!
        let threads: Vec<_> = numbers
            .chunks(num_tasks_per_thread)
            .map(|chunk| scope.spawn(move |_| chunk.iter().cloned().sum::<i32>()))
            .collect();

        let thread_sum: i32 = threads.into_iter().map(|t| t.join().unwrap()).sum();
        let no_thread_sum: i32 = numbers.iter().cloned().sum();

        println!("global sum via threads    : {}", thread_sum);
        println!("global sum single-threaded: {}", no_thread_sum);
    })
    .unwrap();
}

作用域线程 允许您传入保证比线程寿命更长的引用。然后您可以直接使用线程的返回值,跳过通道(这很棒,只是这里不需要!)。

我跟随How can I generate a random number within a range in Rust? 生成随机数。我还将它更改为范围 [1,100],因为我认为这就是您的意思。但是,您的原始代码实际上是 [-98,100],您也可以这样做。

Iterator::sum 用于对数字的迭代器求和。

我输入了线程工作的一些粗略性能数字,忽略了向量构造,处理了 100,000,000 个数字,使用 Rust 1.34 并在发布模式下编译:

| threads | time (ns) | relative time (%) |
|---------+-----------+-------------------|
|       1 |  33824667 |            100.00 |
|       2 |  16246549 |             48.03 |
|       3 |  16709280 |             49.40 |
|       4 |  14263326 |             42.17 |
|       5 |  14977901 |             44.28 |
|       6 |  12974001 |             38.36 |
|       7 |  13321743 |             39.38 |
|       8 |  13370793 |             39.53 |

另见:

【讨论】:

  • 你实际上启动了 5 个线程而不是 4 个。但除此之外:thread::scoped 很酷!不知道它已经按预期工作了。
  • @sellibitze 哈哈,好收获!但是朋友之间的一个额外线程是什么? ^_^。我想它可以通过进行浮点除法然后四舍五入来解决。或者也许做一些trickery with integer division
  • 等一下。我刚刚注意到threads 是一个迭代器!这不是做你想做的事。它会在您要求加入之前懒惰地启动线程。因此,它们不会同时工作。
  • @sellibitze 双倍好收获!我添加了一个collect 来强制线程立即启动。
  • 如何为 ubuntu 安装最新的前沿技术?并感谢您的响应和代码。统一的分布和时间是我最终想要做的。
【解决方案3】:

您的所有任务都会获得scale 变量的副本。线程 1 和 2 都做同样的事情,因为每个线程都有 scale,其值为 0,并以与另一个线程相同的方式对其进行修改。 线程 3 和 4 也是如此。

Rust 可以防止您破坏线程安全。如果scale 被线程共享,那么在访问变量时就会出现竞争条件。

请阅读closures,它们解释了变量复制部分,以及threading,它解释了何时以及如何在线程之间共享变量。

【讨论】:

  • 是的,起初我没有意识到每个线程都有自己的比例副本
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-12-16
  • 2019-10-27
  • 1970-01-01
  • 2015-12-20
  • 2021-10-18
  • 2022-11-16
相关资源
最近更新 更多