Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

English Original

并发:安全的并行计算

你将学到什么: Rust 如何在编译期强制保证线程安全,而 C# 更多依赖约定式做法;Arc<Mutex<T>>lock 的对比,以及 Send/Sync trait。

难度: 高级

在 C# 中,你可以随意在线程间共享任何对象,而你的责任则是确保正确地使用了 lock。而在 Rust 中,编译器会强制保证线程安全。如果你的代码可能会导致数据竞争,它根本就无法通过编译。


共享状态:ArcMutex

在 Rust 中,你不能直接在线程间共享一个普通变量,因为编译器无法证明它是安全的。你需要用到这两个组件:

  1. Arc<T> (原子引用计数器):允许数据在多个线程间共享“所有权”。
  2. Mutex<T> (互斥锁):确保在同一时间内,只能有一个线程可以修改数据。

Rust 示例

#![allow(unused)]
fn main() {
use std::sync::{Arc, Mutex};
use std::thread;

let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
    let counter = Arc::clone(&counter);
    let handle = thread::spawn(move || {
        let mut num = counter.lock().unwrap();
        *num += 1;
    });
    handles.push(handle);
}

for handle in handles { handle.join().unwrap(); }
println!("最终计数结果:{}", *counter.lock().unwrap());
}

SendSync:神奇的标记 Trait

Rust 使用两个特殊的 Trait 来追踪线程安全性:

  • Send:表示该类型可以从一个线程转移到另一个线程。
  • Sync:表示该类型可以安全地在多个线程间通过引用进行共享

杀手锏: 大多数类型会自动实现 SendSync。但如果你尝试在线程中使用非线程安全的类型(比如 Rc),编译器会直接报错。在 C# 中,这类错误通常是无声无息的运行时灾难。


消息传递 (Channels)

Rust 中有一个非常著名的设计哲学:“不要通过共享内存来通信;相反,要通过通信来共享内存。”

#![allow(unused)]
fn main() {
use std::sync::mpsc; // 多生产者,单消费者
let (tx, rx) = mpsc::channel();

thread::spawn(move || {
    tx.send("来自线程的消息").unwrap();
});

let msg = rx.recv().unwrap();
}

C# 开发者总结表

概念C# 方式Rust 方式
简单的锁lock (obj) { ... }MutexGuard (RAII)
共享引用直接传对象引用Arc<T>
线程本地存储[ThreadStatic]thread_local!
并发迭代Parallel.ForEachrayon crate
原子操作Interlockedstd::sync::atomic

练习:并行处理

挑战: 使用 rayon 库(Rust 中进行数据并行的标准库),对一个整数数组中的所有元素并行求平方。

#![allow(unused)]
fn main() {
use rayon::prelude::*;

let mut nums = vec![1, 2, 3, 4, 5];
let squares: Vec<_> = nums.par_iter().map(|&x| x * x).collect();
}

关键理解: “无畏并发 (Fearless Concurrency)”在 Rust 中已成为了现实。编译器就像是一个和你结对编程的资深程序员,在那些微妙的竞态条件甚至还没进入生产阶段之前,它就会把它们全部抓出来。