Rust 语言:线程安全的守护者

104 阅读5分钟

想象一下,你是一名赛车手,正在赛道上风驰电掣。在 Rust 的世界里,你就是那辆赛车,而 Rust 的规则就像是赛道上的护栏和规则,确保你不会失控冲出赛道,撞到观众或者其他赛车。这些规则不仅保护了你的安全,也保护了其他赛车和观众的安全。

在编程的世界里,这种“安全”意味着内存安全和线程安全。Rust 通过它独特的借用检查器和所有权系统,确保了在多线程环境下,数据的访问和修改不会发生冲突,就像是赛车在赛道上各行其道,互不干扰。

曼德博集:数学与艺术的结晶

现在,让我们把话题转向一个既神秘又美丽的数学概念——曼德博集。你可以把它想象成一幅由无数点组成的画作,每个点都代表一个复数。这些复数就像是画布上的颜料,通过一种特殊的“绘画”规则,形成了一幅幅令人惊叹的图案。

开始绘制:Rust 项目的诞生

就像一位画家准备画布和颜料一样,我们首先创建一个新的 Rust 项目,这就像是为画家搭建了一个工作室。所有的创作——代码——都将在这个工作室里完成。

数学之旅:从简单到复杂

在开始绘制之前,我们先来了解一些基础的数学知识。想象一下,你手中有一个弹跳球,每次弹跳后都会落在一个数轴上。如果这个数小于 1 或者大于 1,球就会越弹越远,最终消失在无穷远处。但如果这个数恰好是 1,球就会停留在原地。

现在,如果我们给这个弹跳球增加一点复杂性,让它每次弹跳后不仅落在数轴上,还要加上一个特定的值。这时,球的弹跳轨迹就会变得更加难以预测,但仍然有一定的规律可循。

复数的魔法:从实数到复平面

接下来,我们把视野从一维的数轴扩展到二维的复平面。在这里,每个点不仅有 x 坐标,还有 y 坐标。通过在每次弹跳中加入一个复数,我们可以让弹跳球在复平面上移动,创造出更加复杂和美丽的图案。

曼德博集的定义:寻找不逃逸的点

曼德博集的核心思想是找出那些在复平面上,经过无数次弹跳后仍然不会逃逸到无穷远处的点。这些点就像是被某种神秘力量束缚在原地的精灵,形成了一幅幅独特的图案。

并行计算:多线程的协奏曲

在绘制曼德博集时,我们可以使用 Rust 的并发特性来加速这个过程。想象一下,你有一支由多个画家组成的团队,每个人都负责画布的一部分。通过分工合作,整幅画作很快就能完成。

代码实现:从理论到实践

use num::Complex;
use std::sync::Arc;
use std::thread;

// 尝试确定 `c` 是否位于曼德博集中,最多使用 `limit` 次迭代来判断
fn escape_time(c: Complex<f64>, limit: usize-> Option<usize> {
    let mut z = Complex { re: 0.0, im: 0.0 };
    for i in 0..limit {
        if z.norm_sqr() > 4.0 {
            return Some(i);
        }
        z = z * z + c;
    }
    None
}

// 绘制曼德博集的函数
fn draw_mandelbrot(width: u32, height: u32, limit: usize) {
    let image = Arc::new((0..height).map(|_| vec![0; (width + 7) / 8]).collect::<Vec<Vec<u8>>>());

    // 创建线程池
    let mut handles = vec![];
    for y in 0..height {
        let image_clone = Arc::clone(&image);
        let handle = thread::spawn(move || {
            let py = y as f64 / height as f64 * 2.0 - 1.0;
            for x in 0..width {
                let px = x as f64 / width as f64 * 2.0 - 1.0;
                let c = Complex { re: px, im: py };
                let it = escape_time(c, limit);
                let byte_index = x / 8;
                let bit_index = 7 - (x % 8);
                let value = it.is_some() as u8 * 255;
                image_clone[py as usize][byte_index] |= value << bit_index;
            }
        });
        handles.push(handle);
    }

    // 等待所有线程完成
    for handle in handles {
        handle.join().unwrap();
    }
    // 将计算好的曼德博集数据转换为图像并保存
fn save_image(image: Arc<Vec<Vec<u8>>>, width: u32, height: u32, filename: &str) {
    let mut img = ImageBuffer::new(width, height);

    for (y, row) in image.iter().enumerate() {
        for (x, &value) in row.iter().enumerate() {
            let color = if value == 0 {
                // 集合内的点用黑色表示
                image::Rgb([000])
            } else {
                // 集合外的点用不同亮度的灰色表示
                let brightness = (value as f32 * 255.0).sqrt() as u8;
                image::Rgb([brightness, brightness, brightness])
            };
            img.put_pixel(x as u32, y as u32, color);
        }
    }

    let _ = img.save_with_quality(filename, image::ColorType::RGB8, 80);
}
}

fn main() {
    let argsVec<String> = std::env::args().collect();
    if args.len() < 4 {
        println!("Usage: mandelbrot <width> <height> <limit>");
        return;
    }

    let widthu32 = args[1].parse().unwrap();
    let heightu32 = args[2].parse().unwrap();
    let limitusize = args[3].parse().unwrap();

    draw_mandelbrot(width, height, limit);
    let filename = "mandelbrot.png";
    save_image(Arc::new(image), width, height, filename);
    println!("Saved {}", filename);
}

在这段代码中,我们定义了 save_image 函数,它接受之前计算的 image 数据、图像的宽度和高度,以及要保存的文件名。我们使用 ImageBuffer::new 创建一个新的图像缓冲区,然后遍历 image 数据,将每个像素点的颜色设置为黑色或不同亮度的灰色,取决于它是否属于曼德博集。最后,我们使用 img.save_with_quality 方法将图像以 PNG 格式保存到文件系统中,并指定了图像质量。

请确保在运行这段代码之前,你已经将 image crate 添加到项目的依赖项中,并且你的环境中已经安装了 Rust 编译器和 Cargo。当你运行这个程序时,它将在当前目录下生成一个名为 mandelbrot.png 的图像文件,其中包含了计算得到的曼德博集。