Rust 多线程编程入门:从 thread::spawn 步进入 Rust 并发世界
这是Rust九九八十一难的第六篇。本篇聊下Rust的多线程。为了提高性能,多线程几乎在每种语言中都有应用。多线程思想是一致的,但是Rust有独特的所有权设计,还是有需要注意的地方。

一、简单入门
            
            
              rust
              
              
            
          
          use std::thread;
use std::time::Duration;
fn main() {
    let handles: Vec<_> = (0..4)
        .map(|i| {
            thread::spawn(move || {
                println!("Thread {} start", i);
                thread::sleep(Duration::from_secs(1));
                println!("Thread {} end", i);
            })
        })
        .collect();
    for h in handles {
        h.join().unwrap();
    }
    println!("All done");
}
        1、基本使用:
- 
thread::spawn启动一个新线程,代码中开启4个线程
 - 
move: 把 data 的所有权移动到新线程中;
 - 
join: 等待线程结束并返回结果(如果线程 panic,会返回
Err)。 
2、函数签名
            
            
              rust
              
              
            
          
          pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
    F: FnOnce() -> T + Send + 'static,
    T: Send + 'static,
        | 参数/返回值 | 含义 | 
|---|---|
F | 
线程要执行的函数或闭包 | 
FnOnce() -> T | 
线程的执行逻辑,只能调用一次(所有权会被转移进线程),底层有cas控制的计数保证一次。 | 
Send | 
闭包及返回值都必须是可跨线程安全传递的类型,没有实现这个协议会报错,好多默认实现了 | 
'static | 
保证闭包中捕获的数据在整个线程生命周期内都有效 | 
JoinHandle<T> | 
用于等待线程结束、获取返回值的句柄 | 
3、并发还是并行
Rust 的 std::thread底层是这样的:
- 封装操作系统级线程(OS thread),比如在linux使用pthread_create,window平台调用CreateThread/_beginthreadex;
 - 每个 thread::spawn 调用都会创建一个 真实的内核线程;
 - 线程调度由操作系统控制,而非 Rust 自身。
 
这意味着:
- 如果有多核 CPU(如 8 核),多个线程可能 真正同时执行(即并行);
 - 如果是单核 CPU,线程会轮流切换执行(即并发)。
 
二、Rust并发设计理念
在 C++、Java、Go 里,数据竞争是不是延后到了运行时检查或者开发者自律。而 Rust 想实现的是:"在编译期就证明不会产生数据竞争。","与其让程序在运行时崩溃,不如在编译期拒绝编译",这是Rust与其他语言并发模型的核心区别。在编译期防御数据竞争这块,Rust有三个核心概念:
| 核心机制 | 作用 | 
|---|---|
| 所有权 (Ownership) | 确保资源只有一个拥有者,防止悬垂引用。 | 
| 借用规则 (Borrowing) | 限制并发访问方式(不可变共享 or 独占修改) | 
Send / Sync Trait | 
告诉编译器:这个类型是否能安全地跨线程 | 
靠「类型系统 + 编译期检查」来保证安全,而不是靠运行时锁定或 GC。
1、所有权与借用规则下,根本不允许数据竞争
Rust 默认规则:
- 同一时刻只能有一个可变引用 (
&mut T) - 或多个不可变引用 (
&T) - 二者不能共存。
 
所以在单线程内:
            
            
              ini
              
              
            
          
          let mut x = 10;
let y = &x;      // 不可变借用
let z = &mut x;  // ❌ 错误:不能同时有 & 和 &mut
        编译器直接报错。
2、Send 和 Sync能不能跨线程共享的根本
Rust 用两个 trait 控制多线程安全:
| Trait | 意义 | 示例 | 
|---|---|---|
Send | 
允许被移动到另一个线程 | Vec<T>、i32 | 
Sync | 
允许多个线程同时引用 | &T if T: Sync | 
举例:
i32是Send + Sync→ 安全跨线程共享Rc<T>不是Send(不是线程安全的)Arc<T>是Send + Sync(内部原子计数)
编译器据此拒绝潜在的线程安全错误。所以根本无法写出数据竞争代码 (除非用 unsafe)。下面介绍下具体实现。
三、通信和共享方法
1、线程通信:mpsc 通道(std::sync::mpsc)
"Fearless Concurrency = Ownership + Message Passing " ------ Rust encourages message passing over shared memory.
Rust设计者提供了**(mpsc)** 来进行线程通信。mpsc中Sender 负责发送,Receiver 负责接收。内部通过队列实现(阻塞或非阻塞)。下面是单生产者多消费者代码示例:
            
            
              rust
              
              
            
          
          use std::sync::mpsc;
use std::thread;
fn main() {
    let (tx, rx) = mpsc::channel();
    for i in 0..3 {
        let tx = tx.clone();
        thread::spawn(move || {
            tx.send(format!("Task {} done", i)).unwrap();
        });
    }
    for msg in rx {
        println!("Got: {}", msg);// Got: Task 2 done \n Got: Task 0 done \n Got: Task 1 done
    }
}
        特点
- ✅ 无需共享内存(避免锁),天然线程安全
 - ✅ 零拷贝:多个线程引用同一份数据, 自动计数,安全释放
 - ⚠️ 通信开销更大, 不适合共享复杂结构,需要频繁修改的状态。原因是
send都会 复制(或移动)整个结构体 ,如果结构体很大(如几 MB 的缓存数据),就会产生大量的 内存拷贝。线程之间并不是"共享",而是不断地"发送 + 拷贝 + 接收。如果共享,适合后面的几个共享方案Arc等。 
适用场景:一组任务把结果发给中心线程(日志、聚合等)。
2、Arc<T> --- 多线程只读共享
Arc(Atomic Reference Counted)是线程安全的引用计数智能指针。和Rc类似,但支持多线程。从原理看,Arc内部使用原子操作 (AtomicUsize) 管理计数; 可安全地在多线程中 clone + drop; 共享的是 不可变引用(&T)。
            
            
              rust
              
              
            
          
          use std::sync::Arc;
use std::thread;
fn main() {
    let data = Arc::new(vec![1, 2, 3]);
    for _ in 0..3 {
        let data = Arc::clone(&data);
        thread::spawn(move || {
            println!("{:?}", data);
        });
    }
}
        特点
- ✅ 安全:只读共享,没写操作
 - ✅ 零拷贝:多个线程引用同一份数据, 自动计数,安全释放
 - ⚠️ 不可变:Arc不允许修改 T,所以引入了Arc<Mutex>。
 
适用场景:动态任务(有产生和销毁)、如多线程读取共享配置、缓存、静态表。
3、Arc<Mutex<T>> ------ 多线程可变共享(独占访问)
Arc 负责跨线程共享所有权 ,Mutex 负责内部可变性 + 线程互斥 。组合后实现"多线程可变共享数据"。
如下面示例,当线程调用 .lock() 时,会先获取互斥锁,然后返回 MutexGuard,最后离开作用域自动释放。
            
            
              rust
              
              
            
          
          use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
    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 h in handles {
        h.join().unwrap();
    }
    println!("Result: {}", *counter.lock().unwrap()); //Result: 10
}
        特点
- ✅ 可修改(通过 
lock()获取可变引用),最通用的多线程共享方案 - ✅安全防止数据竞争
 - ⚠️ 低并发时 OK,高并发时可能锁竞争,性能损耗较大
 - ⚠️ 多个锁交叉持有时有死锁风险
 
适合简单共享内存模型(类似 Java 的 synchronized)。
4、Arc<RwLock>:多读单写方案
RwLock(读写锁)允许:多个线程同时读;只有一个线程写。相比Arc<Mutex>,优化了多读。它使用读写锁状态标记:read()则共享读锁计数 +1,write()则独占写锁(等待所有读锁释放)。
            
            
              rust
              
              
            
          
          use std::sync::{Arc, RwLock};
use std::thread;
fn main() {
    let data = Arc::new(RwLock::new(0));
    let readers: Vec<_> = (0..3).map(|_| {
        let d = Arc::clone(&data);
        thread::spawn(move || {
            let r = d.read().unwrap();
            println!("Read: {}", *r);
        })
    }).collect();
    let writer = {
        let d = Arc::clone(&data);
        thread::spawn(move || {
            let mut w = d.write().unwrap();
            *w += 1;
            println!("Write done");
        })
    };
    for r in readers { r.join().unwrap(); }
    writer.join().unwrap();
}
        特点
- ✅ 多读单写性能好,更细粒度控制
 - ⚠️ 写操作会阻塞所有读,不适合高写入频率场景
 
适合多线程频繁读取,偶尔写入(如配置热更新),缓存系统、内存数据库等。
5、DashMap ------ 高性能并发 HashMap(推荐)
DashMap 多分片(sharding)结构:将 HashMap 拆分成多个内部桶,每个桶一个锁;并发访问时,不同键落在不同桶中,不会相互阻塞;内部使用 parking_lot 替代标准锁,性能更高。
安装:
            
            
              ini
              
              
            
          
          [dependencies]
dashmap = "5"
        示例代码:
            
            
              rust
              
              
            
          
          use dashmap::DashMap;
use std::thread;
fn main() {
    let map = DashMap::new();
    let mut handles = vec![];
    for i in 0..4 {
        let map = map.clone();
        handles.push(thread::spawn(move || {
            map.insert(i, i * 10);
        }));
    }
    for h in handles {
        h.join().unwrap();
    }
    for r in map.iter() {
        println!("{:?}", r);
    }
}
        特点
- ✅ 高并发性能极佳,适合 CPU 密集并发
 - ⚠️ 不保证强一致性(瞬时读可能看到旧值),较重
 
适合缓存系统、并发计数器、异步任务共享表,多线程异步 Web 服务共享状态
6、parking_lot::Mutex / RwLock ------ 更快的同步原语
Rust 标准库的 Mutex 使用系统锁实现(例如 Linux futex), 而 parking_lot 使用无锁队列 + 自旋优化,性能提升 30%~50%。
            
            
              rust
              
              
            
          
          use parking_lot::Mutex;
use std::sync::Arc;
use std::thread;
fn main() {
    let data = Arc::new(Mutex::new(0));
    let mut handles = vec![];
    for _ in 0..4 {
        let data = Arc::clone(&data);
        handles.push(thread::spawn(move || {
            *data.lock() += 1;
        }));
    }
    for h in handles {
        h.join().unwrap();
    }
    println!("Result = {}", *data.lock());
}
        适用场景
- 替代标准锁,尤其在高并发或短临界区
 - Tokio、Actix、DashMap 内部都使用它
 
7、Arc<Atomic*> ------ 无锁共享方案
适用于 简单数值或标志 的无锁共享。 Rust 提供 AtomicBool, AtomicUsize, AtomicI64 等类型。
            
            
              rust
              
              
            
          
          use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
fn main() {
    let counter = Arc::new(AtomicUsize::new(0));
    let mut handles = vec![];
    for _ in 0..10 {
        let c = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            c.fetch_add(1, Ordering::SeqCst);
        }));
    }
    for h in handles {
        h.join().unwrap();
    }
    println!("Result = {}", counter.load(Ordering::SeqCst));
}
        特点
- ✅ 无锁,性能最高
 - ⚠️ 不支持复杂结构
 
适用计数器、标志位、任务统计,需要极致性能的低层系统代码
三、线程编排
上面介绍了数据共享,但是现实场景经常有线程先后顺序或者聚合问题,这就涉及到线程编排。Rust 线程确实可以编排(orchestrate),但不像一些高级语言(例如 Go 的 goroutine + channel、或 Java 的 ExecutorService + CompletableFuture)那样内置一整套编排机制。Rust 提供了底层的线程控制能力,而"编排"往往通过以下几种方式实现。
1、std::thread+JoinHandle
Rust 最基础的线程控制方式是用 std::thread::spawn 启动线程,然后用 join() 编排执行顺序:
            
            
              rust
              
              
            
          
          use std::thread;
fn main() {
    let t1 = thread::spawn(|| {
        println!("任务 A");
    });
    let t2 = thread::spawn(|| {
        println!("任务 B");
    });
    // 等待线程结束
    t1.join().unwrap();
    t2.join().unwrap();
    println!("主线程收尾");
}
        特点
- 手动控制线程启动与结束,可以按顺序 join 实现简单编排;
 - 但 join 是阻塞的,灵活性有限。
 
2、通道 (Channel) + 多线程编排
Rust 的 std::sync::mpsc 提供了多生产者单消费者通道,线程之间可以用它传递任务和结果。
            
            
              rust
              
              
            
          
          use std::sync::mpsc;
use std::thread;
fn main() {
    let (tx, rx) = mpsc::channel();
    for i in 0..5 {
        let tx = tx.clone();
        thread::spawn(move || {
            let result = i * 2;
            tx.send(result).unwrap();
        });
    }
    drop(tx); // 关闭发送端
    for msg in rx {
        println!("收到结果: {}", msg);
    }
}
        编排思路:
- 主线程不需要显式 join;
 - 谁先完成谁就先发消息;
 - 可以实现类似"任务调度中心"的结构。
 
3、任务池 (Thread Pool)
如果要批量编排大量线程(比如并发 1000 个任务),直接 spawn 代价太大,可以用线程池。常用库:
threadpoolrayon(最强大的并行任务编排库)- 
`tokio::task::spawn_blocking`\](异步环境中安全执行同步任务)
 
            
            
              rust
              
              
            
          
          use rayon::prelude::*;
fn main() {
    let nums: Vec<_> = (1..=10).collect();
    let squared: Vec<_> = nums.par_iter()
        .map(|x| x * x)
        .collect();
    println!("{:?}", squared);
}
        特点:
- 自动调度任务到线程池,自动 load balancing;
 - 高性能且线程安全;
 
适合批量计算任务
4、基于 Actor / Future 模型的线程编排
在更复杂的系统中,我们通常使用:Actor 模型(如 actix 框架),Future + async runtime(如 tokio 或 async-std)。这类系统让"任务调度"成为核心机制。
示例:Tokio 的任务编排
            
            
              rust
              
              
            
          
          use tokio::task;
#[tokio::main]
async fn main() {
    let h1 = task::spawn(async {
        println!("任务 1");
        10
    });
    let h2 = task::spawn(async {
        println!("任务 2");
        20
    });
    let res1 = h1.await.unwrap();
    let res2 = h2.await.unwrap();
    println!("总和: {}", res1 + res2);
}
        特点:
spawn返回JoinHandle<T>,可以用.await等待;- runtime 负责线程调度;
 - 支持并发编排、超时、取消等复杂逻辑;
 - 类似 Java 的 
CompletableFuture或 Go 的 goroutine。 
适合I/O 密集型系统
四、线程暂停和终止
java面试常问,线程的状态机,如挂起,和终止等,Rust 没有提供类似 Java 的线程状态机 API,也无法查询。这里只说下挂起和终止。
1、挂起线程的方法
Rust 不提供直接挂起线程的 API(像 Java 的 Thread.suspend() 已被废弃)。常用方法是 线程自己控制执行:
方法一:thread::sleep
        
            
            
              rust
              
              
            
          
          use std::thread;
use std::time::Duration;
fn main() {
    let handle = thread::spawn(|| {
        println!("Thread working");
        thread::sleep(Duration::from_secs(3)); // 暂停 3 秒
        println!("Thread resumed");
    });
    handle.join().unwrap();
}
        sleep 让线程阻塞一段时间,模拟暂停
CPU 不会消耗,但线程仍占用栈和调度资源
方法二:条件变量控制暂停/恢复
下面用到了Condvar,简单介绍下。Condvar(Condition Variable)是一种线程同步原语,用于 线程间等待和通知 ,通常和 互斥锁(Mutex) 一起使用,条件状态保存在 Mutex 内部(这里是 bool)。当线程调用 cvar.wait() 或wait_timeout():
- 线程进入阻塞状态(等待条件)
 - CPU 不占用
 - 等待期间释放 
Mutex锁 
当被通知或超时:线程重新获取锁,继续执行后续逻辑。Condvar 是实现"挂起-恢复"模式的安全工具。
            
            
              rust
              
              
            
          
          use std::sync::{Arc, Mutex, Condvar};
use std::thread;
fn main() {
    let pair = Arc::new((Mutex::new(false), Condvar::new()));
    let pair_clone = pair.clone();
    let handle = thread::spawn(move || {
        let (lock, cvar) = &*pair_clone;
        let mut started = lock.lock().unwrap();
        while !*started {
            started = cvar.wait(started).unwrap(); // 阻塞等待信号
        }
        println!("Thread resumed");
    });
    thread::sleep(std::time::Duration::from_secs(2));
    {
        let (lock, cvar) = &*pair;
        let mut started = lock.lock().unwrap();
        *started = true; // 发送信号
        cvar.notify_one();
    }
    handle.join().unwrap();
}
        特点:
- 可实现线程暂停/恢复
 - 安全,不破坏内存或锁
 
2、终止线程的方法
Rust 不允许强制杀死线程 (没有 thread.kill()),因为强制杀线程可能破坏内存安全,可能导致 Mutex 锁未释放,堆资源泄漏。如果退出,可以使用以下方法。
方法一:使用 共享标志
            
            
              rust
              
              
            
          
          use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use std::thread;
use std::time::Duration;
fn main() {
    let stop_flag = Arc::new(AtomicBool::new(false));
    let flag_clone = stop_flag.clone();
    let handle = thread::spawn(move || {
        let mut i = 0;
        while !flag_clone.load(Ordering::Relaxed) {
            println!("Working {}", i);
            i += 1;
            thread::sleep(Duration::from_millis(500));
        }
        println!("Thread exiting safely");
    });
    thread::sleep(Duration::from_secs(2));
    stop_flag.store(true, Ordering::Relaxed); // 主动通知线程退出
    handle.join().unwrap();
}
        方法二:使用 通道通知
            
            
              rust
              
              
            
          
          use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
    let (tx, rx) = mpsc::channel();
    let handle = thread::spawn(move || {
        loop {
            if rx.try_recv().is_ok() { // 收到停止信号
                println!("Thread stopping");
                break;
            }
            println!("Working...");
            thread::sleep(Duration::from_millis(500));
        }
    });
    thread::sleep(Duration::from_secs(2));
    tx.send(()).unwrap(); // 发停止信号
    handle.join().unwrap();
}
        特点:
- 安全退出
 - 资源会自动释放(堆、锁、文件)
 
五、小结
Rust多线程内容较多,本次介绍了thread的使用,做个入门。内容包含共享变量和通信等,挂起和终止,有部分原理和示例代码。跟其他语言一样,线程也存在耗时、资源占用等问题,一些第三方框架有优化方案,这个后续篇章(如 async/await、Tokio)会详细介绍。
如果觉得有用,请点个关注吧,本人公众号大鱼七成饱。