并行

理论上并行和语言并没有什么关系,所以在理论上的并行方式,都可以尝试用Rust来实现。本小节不会详细全面地介绍具体的并行理论知识,只介绍用Rust如何来实现相关的并行模式。

Rust的一大特点是,可以保证“线程安全”。而且,没有性能损失。更有意思的是,Rust编译器实际上只有Send Sync等基本抽象,而对“线程” “锁” “同步” 等基本的并行相关的概念一无所知,这些概念都是由库实现的。这意味着Rust实现并行编程可以有比较好的扩展性,可以很轻松地用库来支持那些常见的并行编程模式。
下面,我们以一个例子来演示一下,Rust如何将线程安全/执行高效/使用简单结合起来的。

在图形编程中,我们经常要处理归一化的问题: 即把一个范围内的值,转换到范围1内的值。比如把一个颜色值255归一后就是1。假设我们有一个表示颜色值的数组要进行归一,用非并行化的方式来处理非常简单,可以自行尝试。下面我们将采用并行化的方式来处理,把数组中的值同时分开给多个线程一起并行归一化处理。

  1. extern crate rayon;
  2. use rayon::prelude::*;
  3. fn main() {
  4. let mut colors = [-20.0f32, 0.0, 20.0, 40.0,
  5. 80.0, 100.0, 150.0, 180.0, 200.0, 250.0, 300.0];
  6. println!("original: {:?}", &colors);
  7. colors.par_iter_mut().for_each(|color| {
  8. let c : f32 = if *color < 0.0 {
  9. 0.0
  10. } else if *color > 255.0 {
  11. 255.0
  12. } else {
  13. *color
  14. };
  15. *color = c / 255.0;
  16. });
  17. println!("transformed: {:?}", &colors);
  18. }

运行结果:

  1. original: [-20, 0, 20, 40, 80, 100, 150, 180, 200, 250, 300]
  2. transformed: [0, 0, 0.078431375, 0.15686275, 0.3137255, 0.39215687, 0.5882353, 0.7058824, 0.78431374, 0.98039216, 1]

以上代码是不是很简单。调用par_iter_mut获得一个并行执行的具有写权限的迭代器,for_each对每个元素执行一个操作。仅此而已。
我们能这么轻松地完成这个任务,原因是我们引入了 rayon 这个库。它把所有的脏活累活都干完了,把清晰安全易用的接口暴露出来给了我们。Rust还可以完全以库的形式,实现异步IO、协程等更加高阶的并行程序开发模式。

为了更深入的加深对Rust并发编程的理解和实践,还安排了一个挑战任务:实现一个Rust版本的MapReduce模式。值得你挑战。