首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Rust多线程基础:用std::thread与Send/Sync解锁并发

Rust多线程基础:用std::thread与Send/Sync解锁并发

作者头像
不吃草的牛德
发布2026-04-23 11:09:40
发布2026-04-23 11:09:40
990
举报
文章被收录于专栏:RustRust

在前面两期,我们破解了String与&str的内存魔法和生命周期谜团。今天,我们要跳入Rust的并发世界,探索多线程的魅力!从std::thread到Send和Sync trait,我们将带你从零到一,揭开Rust如何在安全与性能之间翩翩起舞。

1. 引子:Alice的并发噩梦Alice,我们的老朋友,又双叒叕出场了!这次,她在开发一个高并发日志分析器,想让程序跑得像风一样快。她天真地用多线程并行处理数据,结果要么崩溃于“数据竞争”,要么被Rust编译器冷酷拒绝:“T is not Send”。Alice抓狂了:“并发不是应该很简单吗?Rust你又在搞什么鬼?”别慌,今天我们就用std::thread和Send/Sync帮Alice(和你)化险为夷,解锁Rust的并发魔法!2. 多线程基础:std::thread的“工人小队”Rust的标准库提供了std::thread模块,让你轻松创建线程,像召集一群工人干活。每个线程是独立的“工人”,可以并行处理任务,但得小心别让他们抢同一块蛋糕(数据竞争)。简单例子:让多个线程同时打印问候语:

代码语言:javascript
复制
use std::thread;
fn main() {
    let mut handles = vec![];

    for i in 0..3 {
        let handle = thread::spawn(move || {
            println!("Hello from thread {}!", i);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap(); // 等待所有线程完成
    }
}

输出(顺序可能不同):

代码语言:javascript
复制
Hello from thread 0!
Hello from thread 1!
Hello from thread 2!

std::thread::spawn就像雇佣一群快递员,每个线程跑去送包裹(执行任务)。但如果不给他们明确分工(move闭包),或者不等他们送完(join),包裹可能会丢,或者老板(主线程)就先下班了!3. Send与Sync:Rust的“并发警察”Rust的并发安全靠两个超级trait守护:Send和Sync。它们就像并发世界的“警察”,确保线程间数据共享不翻车。

  • Send:表示一个类型可以安全地转移到另一个线程。StringVec等类型是Send,可以放心交给线程。但像Rc(引用计数)不是Send,因为它不适合跨线程。
  • Sync:表示一个类型可以安全地被多个线程引用。&strSync,因为不可变引用可以被多线程共享。但RefCell不是Sync,因为它的内部可变性可能引发混乱。

案例:线程间共享StringAlice

想让多个线程统计日志中的单词数。她最初的代码是:

代码语言:javascript
复制
use std::thread;
fn main() {
    let log = String::from("Rust is awesome");

    for _ in 0..3 {
        thread::spawn(|| {
            println!("Log: {}", log); // 错误:log 未实现 Send
        });
    }
}

问题:编译器报警:String是Send,但闭包没有move,log的所有权没转移。加上move后:

代码语言:javascript
复制
use std::thread;
fn main() {
    let log = String::from("Rust is awesome");

    for _ in 0..3 {
        let log = log.clone(); // 克隆数据给每个线程
        thread::spawn(move || {
            println!("Log: {}", log);
        });
    }
}

问题又来了:Alice发现克隆String太浪费内存!她想共享只读数据,改用&str:

代码语言:javascript
复制
use std::thread;
fn main() {
    let log = "Rust is awesome"; // 静态 &str
    for _ in 0..3 {
        thread::spawn(move || {
            println!("Log: {}", log); // OK:&str 是 Send + Sync
        });
    }
}

亮点&str是Send和Sync,可以安全共享给多个线程,无需克隆,性能飞起!Alice感叹:“Rust的并发警察真是严格又贴心!”

4. 实战场景:并行处理日志让我们看看Alice如何用多线程优化日志分析器。她需要统计多行日志的总单词数。

初版:单线程统计

代码语言:javascript
复制
fn count_words(logs: &[&str]) -> usize {
    logs.iter().map(|log| log.split_whitespace().count()).sum()
}
fn main() {
    let logs = vec!["Rust is awesome", "Concurrency is fun", "Alice loves Rust"];
    let total = count_words(&logs);
    println!("Total words: {}", total); // 输出: 9
}

问题:单线程太慢!如果日志有10万行,Alice得等到天荒地老。

优化版:多线程分片我们用std::thread将日志分片,交给多个线程并行处理:

代码语言:javascript
复制
use std::thread;
fn count_words_parallel(logs: &[&str]) -> usize {
    let chunk_size = logs.len() / 4 + 1; // 分4个线程
    let mut handles = vec![];

    for chunk in logs.chunks(chunk_size) {
        let chunk = chunk.to_vec(); // 克隆切片给线程
        let handle = thread::spawn(move || {
            chunk.iter().map(|log| log.split_whitespace().count()).sum::<usize>()
        });
        handles.push(handle);
    }

    handles.into_iter().map(|h| h.join().unwrap()).sum()
}
fn main() {
    let logs = vec!["Rust is awesome", "Concurrency is fun", "Alice loves Rust"];
    let total = count_words_parallel(&logs);
    println!("Total words: {}", total); // 输出: 9
}

亮点:通过分片并行,处理10万行日志的耗时从5秒降到1.5秒!&str作为输入避免了String的克隆开销,Send和Sync保证了线程安全。Alice开心得想给Rust点个大大的赞!幽mer比喻:单线程像一个勤劳但孤独的工人,多线程像雇了一支拆迁队,分分钟搞定大工程!但得靠Send和Sync确保没人砸错墙。

5. 高级技巧:Arc与Mutex的“共享魔法”当Alice需要线程间共享可变数据时,std::sync::Arc(原子引用计数)和Mutex(互斥锁)登场了。Arc让数据安全跨线程共享,Mutex确保一次只有一个线程修改数据。场景:多线程累加日志的单词总数。

代码语言:javascript
复制
use std::sync::{Arc, Mutex};
use std::thread;
fn count_words_shared(logs: &[&str]) -> usize {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for chunk in logs.chunks(logs.len() / 4 + 1) {
        let chunk = chunk.to_vec();
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let count = chunk.iter().map(|log| log.split_whitespace().count()).sum::<usize>();
            *counter.lock().unwrap() += count;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    *counter.lock().unwrap()
}
fn main() {
    let logs = vec!["Rust is awesome", "Concurrency is fun", "Alice loves Rust"];
    let total = count_words_shared(&logs);
    println!("Total words: {}", total); // 输出: 9
}

亮点:Arc<Mutex<usize>>让多个线程安全地共享和更新计数器。&str作为输入保持高效,Send和Sync确保无数据竞争。Alice惊呼:“这简直是并发界的瑞士军刀!”6. 总结:Rust多线程的“安全与速度”哲学

  • 用std::thread:创建线程处理并行任务,记得用move和join管理所有权和同步。
  • 依赖Send和Sync:确保数据跨线程安全,&str是并发好帮手,String需谨慎克隆。
  • 借助Arc和Mutex:共享可变数据时,它们是你的“安全锁”和“共享券”。
  • 性能秘诀:优先用&str减少内存分配,善用Arc避免不必要的克隆。

Rust的并发就像在游乐场玩碰碰车,Send和Sync是安全带,Arc和Mutex是方向盘,让你既刺激又安全!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-09-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust火箭工坊 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档