🊀 Rust 并发猖皋

fearless concurrency — Rust 的所有权系统圚猖译期消灭数据竞争让并发从"小心翌翌"变成"攟心倧胆"。从 OS 线皋到 async/await从 Mutex 到 tokio这篇文章芆盖 Rust 并发的完敎囟景。

Send/Sync 线皋 Channel Mutex/RwLock async/await tokio Actor

📑 目圕

  1. 并发 vs 并行 — 抂念区分䞎 Rust 的猖译期保证
  2. 线皋 — std::thread 䞎线皋池
  3. 消息䌠递 — mpsc 侎 crossbeam channel
  4. 共享状态 — Mutex、RwLock、Arc 侎 Atomic
  5. Sync Primitives — Barrier、Condvar、OnceLock
  6. async/await — Future、Pin 侎 Waker
  7. tokio 入闚 — Runtime、spawn、select!
  8. 匂步暡匏 — Actor、Stream、Watch、信号量限流
  9. 并发安党 — 数据竞争、死锁䞎锁顺序
  10. 实战并发 Web 爬虫

1. 并发 vs 并行 — 抂念区分䞎 Rust 的猖译期保证

栞心抂念

埈倚人混甚"并发"和"并行"䜆它们是䞍同的抂念

✅ 并发 (Concurrency)

倚䞪任务圚逻蟑䞊同时掚进

  • 单栞也胜并发时闎片亀替
  • 关泚结构劂䜕组织代码倄理倚任务
  • 关键词亀替、调床、结构
  • 类比䞀䞪人同时倄理倚䞪项目圚它们之闎切换

✅ 并行 (Parallelism)

倚䞪任务圚物理䞊同时执行

  • 必须倚栞/倚倄理噚
  • 关泚执行劂䜕利甚硬件同时运行
  • 关键词同时、硬件、执行
  • 类比倚䞪人同时各自倄理䞀䞪项目

🎯 关键掞察并发是关于讟计的并行是关于执行的。䜠可以有并发无并行单栞倚任务也可以有并行无并发独立进皋各干各的。最奜的情况并发讟计 + 并行执行。

Rust 的猖译期保证Send 侎 Sync

Rust 最区倧的䞀点它把并发安党检查前移到了猖译期。䞍是运行时报错䞍是测试时发现而是猖译郜过䞍了。䞀䞪栞心 marker trait

📀 Send trait

类型 T 实现了 Send衚瀺可以安党地跚线皋移劚所有权。倧倚数类型自劚实现 Send䟋倖Rc<T>、RawFd 等。

Send 倌可以从线皋 A move 到线皋 B

📥 Sync trait

类型 T 实现了 Sync衚瀺可以安党地跚线皋共享匕甚即 &T 是 Send 的。Cell<T>、RefCell<T> 䞍实现 Sync。

Sync &T 可以从线皋 A 共享给线皋 B

自劚掚富规则
// Send/Sync 是 auto trait —— 猖译噚自劚掚富
// 规则埈简单所有字段郜 Send → 结构䜓 Send所有字段郜 Sync → 结构䜓 Sync

struct Safe { data: Vec<String> }  // ✅ 自劚 Send + Sync

struct NotSync { cell: Cell<usize> }  // ❌ 䞍实现 SyncCell 䞍是 Sync

struct NotSend { rc: Rc<String> }     // ❌ 䞍实现 SendRc 䞍是 Send

// 手劚标记仅圚确定安党时䜿甚
struct ForceSend(*const u8);
unsafe impl Send for ForceSend {}  // unsafe䜠向猖译噚保证它是安党的

⚠ unsafe impl Send/Sync 是䜠向猖译噚䞋的"军什状"。猖译噚䞍再检查䞀切后果自莟。倧倚数时候䜠䞍需芁手劚实现——劂果猖译噚䞍让䜠 Send它倧抂率是对的。

Send 侎 Sync 的关系

Send & Sync 的关系眑 T: Sync ──────────────> &T: Send (T 可跚线皋共享匕甚) (T 的匕甚可跚线皋移劚) T: Send ──────────────> Arc<T>: Send + Sync (T 可跚线皋移劚) (Arc 把 T 包成可共享的) Rc<T> ──── 䞍 Send ────> Arc<T> ──── Send + Sync (单线皋匕甚计数) (原子匕甚计数) Cell/RefCell ──── 䞍 Sync ────> Mutex/RwLock ──── Sync (单线皋内郚可变性) (倚线皋内郚可变性)

2. 线皋 — std::thread 䞎线皋池

std::thread::spawn

Rust 的原生线皋是 OS 线皋1:1 暡型由操䜜系统盎接调床。创建线皋甚 std::thread::spawn

基础线皋创建
use std::thread;
use std::time::Duration;

fn main() {
    // spawn 返回 JoinHandle<T>
    let handle = thread::spawn(|| {
        for i in 1..=5 {
            println!("子线皋: 第 {} 次打印", i);
            thread::sleep(Duration::from_millis(100));
        }
        "子线皋完成"  // 返回倌
    });

    // 䞻线皋继续执行
    for i in 1..=3 {
        println!("䞻线皋: 第 {} 次打印", i);
        thread::sleep(Duration::from_millis(100));
    }

    // join 等埅子线皋结束获取返回倌
    let result = handle.join().unwrap();
    println!("子线皋返回: {}", result);
}

move 闭包

闭包默讀按匕甚捕获变量䜆线皋可胜比创建它的䜜甚域掻埗曎久。Rust 芁求跚线皋闭包必须 move把所有权蜬移到新线皋

move 闭包
use std::thread;

fn main() {
    let name = String::from("Alice");
    let age = 30;

    // ❌ 猖译错误name 的匕甚可胜圚䞻线皋释攟后仍圚子线皋䞭䜿甚
    // thread::spawn(|| println!("Hello, {}", name));

    // ✅ move 把 name 和 age 的所有权蜬移到子线皋
    let handle = thread::spawn(move || {
        println!("Hello, {}! You are {} years old.", name, age);
        // name 和 age 圚这里被消莹䞻线皋䞍胜再䜿甚
    });

    handle.join().unwrap();
}

线皋闎数据䌠递

劂䜕把数据从创建者䌠给新线皋䜆又䞍想 move 敎䞪变量几种方匏

方匏场景匀销
move 闭包所有权蜬移简单盎接零匀销
channel流匏䌠递生产者-消莹者䞀次 clone
Arc<T>共享只读数据原子匕甚计数
thread::scope䞎时线皋匕甚倖层变量零匀销

thread::scope — 安党的匕甚䌠递

Rust 1.63 匕入的 thread::scope 解决了经兞痛点劂䜕圚子线皋䞭匕甚倖层变量而䞍甚担心生呜呚期scope 保证所有子线皋圚䜜甚域结束前退出

thread::scope
use std::thread;

fn main() {
    let data = vec![1, 2, 3, 4, 5, 6, 7, 8];

    // scope 内创建的线皋圚 scope 结束时自劚 join
    thread::scope(|s| {
        // 切片匕甚可以安党䌠入因䞺 scope 保证线皋圚 scope 结束前退出
        let chunk1 = &data[0..4];
        let chunk2 = &data[4..8];

        s.spawn(|| {
            let sum: i32 = chunk1.iter().sum();
            println!("前半求和: {}", sum);
        });

        s.spawn(|| {
            let sum: i32 = chunk2.iter().sum();
            println!("后半求和: {}", sum);
        });
    }); // ← 这里自劚 join 所有线皋data 䞍䌚悬空

    println!("党郚完成data 仍可甚: {:?}", data);
}

线皋池 — rayon

手劚管理线皋埈繁琐rayon 提䟛了工䜜窃取work-stealing线皋池让数据并行变埗极其简单

rayon 数据并行
use rayon::prelude::*;  // 匕入并行迭代噚 trait

fn main() {
    let data: Vec<u64> = (1..=10_000_000).collect();

    // 单线皋迭代
    let sum_seq: u64 = data.iter().sum();

    // 并行迭代 —— 只需把 iter() 改成 par_iter()
    let sum_par: u64 = data.par_iter().sum();

    assert_eq!(sum_seq, sum_par);

    // 曎实甚的䟋子并行倄理
    let results: Vec<String> = data
        .par_iter()
        .filter(|&&x| x % 2 == 0)
        .map(|&x| format!("even: {}", x))
        .collect();
}

✅ rayon 的魔力把 iter() 改成 par_iter()其他代码䞀行䞍甚改。rayon 自劚把任务分配到线皋池倄理工䜜窃取、莟蜜均衡和结果收集。圚倚栞机噚䞊通垞胜获埗接近线性的加速比。

3. 消息䌠递 — mpsc 侎 crossbeam channel

Rust 瀟区有䞀䞪著名的口号"Do not communicate by sharing memory; instead, share memory by communicating." 消息䌠递是 Rust 并发的栞心范匏之䞀。

std::sync::mpsc — 标准库 channel

mpsc = Multi-Producer, Single-Consumer。䞀䞪接收者倚䞪发送者

mpsc 基础甚法
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
    // channel() 返回 (Sender, Receiver)
    let (tx, rx) = mpsc::channel();

    // 倚生产者clone sender
    let tx1 = tx.clone();
    let tx2 = tx.clone();

    // 生产者 1
    thread::spawn(move || {
        let msgs = vec!["任务A-1", "任务A-2", "任务A-3"];
        for msg in msgs {
            tx1.send(msg).unwrap();
            thread::sleep(Duration::from_millis(100));
        }
    });

    // 生产者 2
    thread::spawn(move || {
        let msgs = vec!["任务B-1", "任务B-2"];
        for msg in msgs {
            tx2.send(msg).unwrap();
            thread::sleep(Duration::from_millis(150));
        }
    });

    // drop 原始 sender吊则 rx.iter() 䌚氞远阻塞
    drop(tx);

    // 消莹者迭代接收盎到所有 sender 被 drop
    for received in rx.iter() {
        println!("收到: {}", received);
    }
    println!("所有生产者已断匀接收结束");
}

bounded vs unbounded

unbounded channel

let (tx, rx) = mpsc::channel();
// 或 mpsc::unbounded()

// ✅ send 氞远䞍阻塞
// ⚠ 劂果消莹者慢内存䌚无限增长
// 适合生产者速率可控、消莹速床皳定

bounded channel

let (tx, rx) = mpsc::sync_channel(100);
// 容量䞺 100

// ✅ 满了之后 send 䌚阻塞背压
// ✅ 防止内存无限增长
// 适合生产者速率䞍确定、需芁背压
// send_timeout() 可讟超时
// try_send() 可非阻塞发送

crossbeam channel — 曎快曎灵掻

crossbeam-channel 是标准库 mpsc 的高性胜替代

crossbeam channel
use crossbeam_channel as channel;
use std::thread;

fn main() {
    // unbounded
    let (tx, rx) = channel::unbounded();

    // bounded支持 select!
    let (tx_b, rx_b) = channel::bounded(100);

    // crossbeam 的 Receiver 实现了 Iterator
    // 曎重芁的是Sender 和 Receiver 郜是 Send + Sync
    // 可以圚倚线皋闎自由共享

    thread::spawn(move || {
        tx.send("hello from crossbeam").unwrap();
    });

    // recv() 阻塞接收try_recv() 非阻塞
    let msg = rx.recv().unwrap();
    println!("{}", msg);

    // select! —— 同时等埅倚䞪 channel
    let (tx1, rx1) = channel::bounded(1);
    let (tx2, rx2) = channel::bounded(1);

    crossbeam_channel::select! {
        recv(rx1) -> msg => println!("从 rx1 收到: {:?}", msg),
        recv(rx2) -> msg => println!("从 rx2 收到: {:?}", msg),
    }
}
特性std::sync::mpsccrossbeam-channel
性胜䞭等曎快无锁内郚实现
Sender Clone✅✅
Receiver Clone❌单消莹者✅倚消莹者 MPMC
select!❌✅
bounded + unbounded✅✅
Send + Sync仅 SenderSender + Receiver
额倖䟝赖无需加 crate
Channel 架构对比 std::sync::mpsc ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Sender 1 │ │ Sender 2 │ │ Sender 3 │ Multi-Producer └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ └──────────────┌──────────────┘ â–Œ ┌─────────────┐ │ Channel │ └──────┬──────┘ â–Œ ┌─────────────┐ │ Receiver │ Single-Consumer └─────────────┘ crossbeam-channel ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Sender 1 │ │ Sender 2 │ │ Sender 3 │ Multi-Producer └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────────────┌──────────────┘ â–Œ ┌─────────────┐ │ Channel │ └──────┬──────┘ ┌──────────────┌──────────────┐ â–Œ â–Œ â–Œ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ Receiver1 │ │ Receiver2 │ │ Receiver3 │ Multi-Consumer (MPMC) └───────────┘ └───────────┘ └───────────┘

4. 共享状态 — Mutex、RwLock、Arc 侎 Atomic

Mutex<T> — 互斥锁

圓倚䞪线皋需芁修改同䞀仜数据时Mutex<T> 是最盎接的工具。Rust 的 Mutex 讟计哲孊数据䞎锁绑定䞍拿到锁就访问䞍到数据

Mutex 基础
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    // Mutex 包裹需芁保技的数据
    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 || {
            // lock() 返回 MutexGuard自劚 unlock
            let mut num = counter.lock().unwrap();
            *num += 1;
            // MutexGuard 圚这里 drop自劚释攟锁
        });
        handles.push(handle);
    }

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

    println!("结果: {}", *counter.lock().unwrap());  // 10
}

🎯 Rust Mutex 的粟劙讟计锁和数据绑定圚䞀起——Mutex<T> 拥有数据。䜠无法圚䞍 lock() 的情况䞋访问内郚的 T。这比 C 的 pthread_mutex锁和数据是分犻的忘了加锁盎接访问也䞍䌚报错安党埗倚。

RwLock<T> — 读写锁

读倚写少的场景RwLock 允讞倚䞪读者同时持有锁䜆写者独占

RwLock 读写分犻
use std::sync::{Arc, RwLock};
use std::thread;

fn main() {
    let data = Arc::new(RwLock::new(vec![1, 2, 3]));

    // 倚䞪读者可以同时持有 read lock
    let mut readers = vec![];
    for i in 0..5 {
        let data = Arc::clone(&data);
        readers.push(thread::spawn(move || {
            let r = data.read().unwrap();  // RwLockReadGuard
            println!("读者 {}: {:?}", i, *r);
        }));
    }

    // 写者需芁独占访问
    let data_w = Arc::clone(&data);
    let writer = thread::spawn(move || {
        let mut w = data.write().unwrap();  // RwLockWriteGuard
        w.push(4);
        println!("写者: 添加了元玠");
    });

    for r in readers { r.join().unwrap(); }
    writer.join().unwrap();

    println!("最终数据: {:?}", *data.read().unwrap());
}

Arc<T> — 原子匕甚计数

Arc = Atomic Reference Counted。线皋安党的 Rc甚于倚线皋共享所有权

Arc + Mutex 经兞组合
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    // Arc: 让倚䞪线皋"共同拥有"同䞀䞪 Mutex
    // Mutex: 保技内郚数据的安党访问
    let shared = Arc::new(Mutex::new(Vec::new()));

    let mut handles = vec![];
    for i in 0..4 {
        let shared = Arc::clone(&shared);  // 匕甚计数 +1原子操䜜
        handles.push(thread::spawn(move || {
            let mut data = shared.lock().unwrap();
            data.push(format!("来自线皋 {}", i));
        }));
    }

    for h in handles { h.join().unwrap(); }
    println!("{:?}", *shared.lock().unwrap());
}

Rc<T>

  • 单线皋匕甚计数
  • 䞍 Send 䞍 Sync
  • 匀销曎䜎非原子操䜜
  • 甚于单线皋囟/树结构

Arc<T>

  • 原子匕甚计数
  • Send圓 T: Send + Sync
  • 匀销略高原子操䜜
  • 甚于倚线皋共享数据

Atomic 类型 — 无锁并发

对于简单的计数噚、标志䜍Atomic 类型比 Mutex 曎高效——䞍需芁获取锁盎接甚 CPU 的原子指什

Atomic 类型
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;

fn main() {
    let counter = Arc::new(AtomicUsize::new(0));
    let done = Arc::new(AtomicBool::new(false));

    let mut handles = vec![];
    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let done = Arc::clone(&done);
        handles.push(thread::spawn(move || {
            // fetch_add 返回旧倌原子地执行 += 1
            counter.fetch_add(1, Ordering::SeqCst);
        }));
    }

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

    // load 原子地读取倌
    println!("计数噚: {}", counter.load(Ordering::SeqCst));  // 10

    // compare_exchange —— CAS 操䜜
    let old = 0;
    let new = 42;
    counter.compare_exchange(old, new, Ordering::SeqCst, Ordering::SeqCst);
}

Ordering — 内存序

Ordering保证性胜䜿甚场景
Relaxed无顺序保证仅原子性最快计数噚、统计
Acquire / ReleaseRelease 之前的写对 Acquire 之后的读可见䞭等锁、flag 通知
AcqRelAcquire + Release侭等read-modify-write
SeqCst党局顺序䞀臎最慢需芁䞥栌顺序时

⚠ 实甚建议䞍确定甚什么 Ordering 时甚 SeqCst。性胜差匂圚绝倧倚数场景䞋可以応略。只有圓䜠 profiling 发现原子操䜜是瓶颈 时才考虑降级到曎宜束的 Ordering。

5. Sync Primitives — Barrier、Condvar、OnceLock

Barrier — 栅栏同步

倚䞪线皋需芁圚某䞪点汇合后再䞀起继续甚 Barrier

Barrier 栅栏同步
use std::sync::{Arc, Barrier};
use std::thread;

fn main() {
    let n_threads = 4;
    let barrier = Arc::new(Barrier::new(n_threads));

    let mut handles = vec![];
    for i in 0..n_threads {
        let barrier = Arc::clone(&barrier);
        handles.push(thread::spawn(move || {
            println!("线皋 {} 阶段1 完成", i);
            // 所有线皋郜到这里后䞀起继续
            barrier.wait();
            println!("线皋 {} 阶段2 匀始", i);
        }));
    }

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

Condvar — 条件变量

圓线皋需芁等埅某䞪条件成立时Condvar + Mutex 是经兞组合

Condvar 条件变量
use std::sync::{Arc, Mutex, Condvar};
use std::thread;
use std::time::Duration;

fn main() {
    // Condvar 总是和 Mutex 配对䜿甚
    let pair = Arc::new((Mutex::new(false), Condvar::new()));
    let pair2 = Arc::clone(&pair);

    // 等埅线皋
    let waiter = thread::spawn(move || {
        let (lock, cvar) = &*pair2;
        let mut started = lock.lock().unwrap();
        // wait 䌚1. 释攟锁 2. 挂起 3. 被 notify 后重新获取锁
        while !*started {
            started = cvar.wait(started).unwrap();
        }
        println!("等埅线皋: 条件满足继续执行");
    });

    // 通知线皋
    thread::spawn(move || {
        let (lock, cvar) = &*pair;
        thread::sleep(Duration::from_secs(1));
        {
            let mut started = lock.lock().unwrap();
            *started = true;
        }
        // notify_one 唀醒䞀䞪等埅线皋
        cvar.notify_one();
        // cvar.notify_all() 唀醒所有等埅线皋
    }).join().unwrap();

    waiter.join().unwrap();
}

OnceLock / Once — 䞀次性初始化

党局只初始化䞀次的倌单䟋暡匏、党局配眮Rust 提䟛了安党的原语

OnceLock 侎 Once
use std::sync::{OnceLock, Once};
use std::thread;

// OnceLock: 延迟初始化的党局变量
static DATABASE: OnceLock<String> = OnceLock::new();

fn get_database() -> &'static str {
    DATABASE.get_or_init(|| {
        println!("初始化数据库连接...");
        String::from("postgresql://localhost:5432/mydb")
    })
}

// Once: 只执行䞀次的代码块
static INIT: Once = Once::new();

fn initialize() {
    INIT.call_once(|| {
        println!("这段代码党局只执行䞀次");
    });
}

fn main() {
    // 倚线皋同时调甚 get_database只有第䞀䞪䌚真正初始化
    let mut handles = vec![];
    for _ in 0..5 {
        handles.push(thread::spawn(|| {
            let db = get_database();
            println!("获取到: {}", db);
        }));
    }
    for h in handles { h.join().unwrap(); }
}

✅ OnceLock vs lazy_static / once_cellRust 1.70 把 OnceLock 皳定进了标准库基本可以替代 lazy_static! 和 once_cell::sync::LazyLock。对于简单场景䌘先甚标准库。

6. async/await — Future、Pin 侎 Waker

䞺什么需芁 async

OS 线皋的问题

Async 的答案蜻量级任务圚甚户态调床䞀䞪任务只需几癟字节蜻束支持癟䞇并发。

OS 线皋暡型

线皋1 ──[等埅I/O]──→ 阻塞内栞调床噚挂起
线皋2 ──[等埅I/O]──→ 阻塞
线皋3 ──[计算䞭]──→ 运行

内存: 每线皋 ~2MB 栈
切换: 内栞态 (~1-10ÎŒs)
è§„æš¡: 几千线皋
适合: CPU 密集型

Async 任务暡型

任务1 ──[等埅I/O]──→ 挂起甚户态 yield
任务2 ──[等埅I/O]──→ 挂起
任务3 ──[计算䞭]──→ 运行

内存: 每任务 ~几癟字节
切换: 甚户态 (~0.2-1ÎŒs)
è§„æš¡: 癟䞇任务
适合: I/O 密集型

Future trait

䞀切匂步的基础是 Future trait

Future trait 定义
pub trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

pub enum Poll<T> {
    Ready(T),     // 完成返回结果
    Pending,      // 未完成皍后再 poll
}

关键理解Future 是惰性的——创建它䞍䌚执行任䜕代码。必须被 poll 才䌚掚进。圓䜠 .await 时实际䞊是

  1. poll 这䞪 Future
  2. 劂果返回 Pending泚册 Waker圓前任务挂起
  3. 圓 I/O 就绪Waker 被调甚任务被重新 poll
  4. 盎到返回 Ready(value)

async fn 侎 .await

async/await 基础
// async fn 返回䞀䞪 impl Future
async fn fetch_user(id: u32) -> User {
    let response = http_get(format!("/api/users/{}", id)).await;  // 等埅 I/O
    let user: User = response.json().await;  // 等埅解析
    user  // 返回倌
}

// 等价于
fn fetch_user(id: u32) -> impl Future<Output = User> {
    async move {
        let response = http_get(format!("/api/users/{}", id)).await;
        let user: User = response.json().await;
        user
    }
}

// async block —— 䞍需芁凜数盎接写
let future = async {
    let a = fetch_user(1).await;
    let b = fetch_user(2).await;
    (a, b)
};

Pin — 䞺什么需芁固定䜍眮

async fn 猖译后生成䞀䞪状态机。劂果 Future 圚 .await 点之闎保存了匕甚指向自身数据那么移劚这䞪 Future 䌚让匕甚悬空。Pin 就是保证"䞀旊匀始 poll就䞍胜移劚"

Pin 解决的问题 async fn 瀺䟋 async fn example() { let data = vec![1, 2, 3]; // 圚栈䞊分配 let ref_to_data = &data; // 匕甚指向自身字段 something().await; // ← 挂起点 println!("{:?}", ref_to_data); // .await 之后䜿甚匕甚 } 问题劂果 Future 圚 .await 期闎被移劚比劂从栈移到堆 ref_to_data 就䌚指向无效内存 解决Pin<&mut Future> 保证 Future 的内存地址䞍再改变 匕甚始终有效。这是 Rust 的猖译期保证——自匕甚结构必须 Pin。 状态机蜬换 ┌─────────┐ .await 挂起 ┌──────────────┐ poll 恢倍 ┌─────────┐ │ State 0 │ ──────────────→ │ State 1 │ ────────────→ │ State 2 │ │ (初始) │ │ (保存匕甚) │ │ (完成) │ └─────────┘ └──────────────┘ └─────────┘

Waker — 谁来重新 poll

圓 Future 返回 Pending它需芁告诉运行时"什么时候再 poll 我"。这就是 Waker 的䜜甚

Waker 工䜜原理抂念级
// 圓 Future 返回 Pending 时
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Output> {
    if self.is_ready() {
        Poll::Ready(self.take_output())
    } else {
        // 把 waker 泚册到 I/O 来源epoll/kqueue/...
        // 圓 I/O 就绪时epoll 䌚调甚 waker.wake()
        // wake() 通知运行时重新 poll 这䞪 Future
        self.register_waker(cx.waker());
        Poll::Pending
    }
}

// 流皋
// 1. 运行时 poll Future → Pending
// 2. Future 泚册 Waker
// 3. I/O 就绪 → Waker.wake() 被调甚
// 4. 运行时重新 poll Future → Ready 或 Pending埪环

🎯 日垞匀发䞍需芁手劚实现 Future/Pin/Waker。99% 的场景䞋䜠只需芁写 async fn 和 .await运行时tokio/async-std䌚倄理所有底层细节。理解这些抂念是䞺了出问题时胜看懂报错而䞍是日垞手写 poll。

7. tokio 入闚 — Runtime、spawn、select!

Runtime — 匂步䞖界的操䜜系统

tokio 是 Rust 最䞻流的匂步运行时盞圓于匂步䞖界的"操䜜系统"——它管理任务调床、I/O 驱劚、计时噚等

tokio Runtime 配眮
#[tokio::main]  // 最垞甚倚线皋运行时
async fn main() {
    println!("Hello, tokio!");
}

// 等价于手劚创建
fn main() {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
        println!("Hello, tokio!");
    });
}

// 䞀种 Runtime
// 1. 倚线皋默讀—— 适合倧倚数场景工䜜窃取调床
#[tokio::main]
async fn main() { /* ... */ }

// 2. 圓前线皋 —— 适合蜻量/嵌入匏场景
#[tokio::main(flavor = "current_thread")]
async fn main() { /* ... */ }
tokio Runtime 架构 ┌───────────────────────────────────────────────────────┐ │ tokio Runtime │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ Worker │ │ Worker │ │ Worker │ │ Worker │ │ │ │ Thread │ │ Thread │ │ Thread │ │ Thread │ │ │ │ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │ │ ┌─────┐ │ │ │ │ │Task1│ │ │ │Task3│ │ │ │Task5│ │ │ │Task7│ │ │ │ │ │Task2│ │ │ │Task4│ │ │ │Task6│ │ │ │Task8│ │ │ │ │ └─────┘ │ │ └─────┘ │ │ └─────┘ │ │ └─────┘ │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ │ │ │ │ │ └────────────┮────────────┮────────────┘ │ │ Work Stealing │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │ │ I/O Driver │ │ Timer │ │ Blocking Pool │ │ │ │(epoll/ │ │ (时闎蜮) │ │ (spawn_blocking) │ │ │ │ kqueue) │ │ │ │ │ │ │ └──────────┘ └──────────┘ └──────────────────┘ │ └───────────────────────────────────────────────────────┘

tokio::spawn — 创建匂步任务

spawn 侎 JoinHandle
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    // spawn 创建䞀䞪匂步任务返回 JoinHandle
    let handle = tokio::spawn(async {
        sleep(Duration::from_secs(1)).await;
        println!("匂步任务完成");
        42
    });

    // 做其他事情...
    println!("䞻任务继续执行");

    // await JoinHandle 获取结果
    let result = handle.await.unwrap();  // 泚意.await 䞍是 .join()
    println!("结果: {}", result);
}

⚠ 'static 纊束tokio::spawn 芁求 Future 是 'static 的——䞍胜借甚倖郚变量。需芁甚 move 蜬移所有权或者甚 Arc 共享。

tokio::select! — 同时等埅倚䞪 Future

select! 是 tokio 最区倧的组合噚之䞀同时等埅倚䞪匂步操䜜谁先完成就倄理谁

select! 倚路倍甚
use tokio::sync::mpsc;
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    let (tx1, mut rx1) = mpsc::channel(32);
    let (tx2, mut rx2) = mpsc::channel(32);

    // 启劚䞀䞪生产者
    tokio::spawn(async move {
        tx1.send("消息来自 channel 1").await.unwrap();
    });
    tokio::spawn(async move {
        sleep(Duration::from_millis(50)).await;
        tx2.send("消息来自 channel 2").await.unwrap();
    });

    // select! 谁先到倄理谁
    tokio::select! {
        msg = rx1.recv() => {
            println!("收到 rx1: {:?}", msg);
        }
        msg = rx2.recv() => {
            println!("收到 rx2: {:?}", msg);
        }
    }

    // select! 埪环 —— 倄理倚䞪事件流
    loop {
        tokio::select! {
            msg = rx1.recv() => {
                match msg {
                    Some(m) => println!("rx1: {}", m),
                    None => break,  // channel 关闭
                }
            }
            msg = rx2.recv() => {
                match msg {
                    Some(m) => println!("rx2: {}", m),
                    None => break,
                }
            }
        }
    }
}

JoinSet — 管理倚䞪任务

JoinSet 批量任务管理
use tokio::task::JoinSet;

#[tokio::main]
async fn main() {
    let mut set = JoinSet::new();

    // 批量 spawn
    for i in 0..5 {
        set.spawn(async move {
            tokio::time::sleep(Duration::from_millis(i * 100)).await;
            format!("任务 {} 完成", i)
        });
    }

    // 按完成顺序收集结果
    while let Some(result) = set.join_next().await {
        println!("{}", result.unwrap());
    }
}

任务取消

tokio 任务的取消通过 AbortHandle 或 CancellationToken 实现

任务取消
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    // 方匏1: AbortHandle
    let handle = tokio::spawn(async {
        loop {
            sleep(Duration::from_secs(1)).await;
            println!("工䜜䞭...");
        }
    });

    let abort_handle = handle.abort_handle();
    // 5秒后取消
    sleep(Duration::from_secs(5)).await;
    abort_handle.abort();  // 或 handle.abort()
    let _ = handle.await;  // Err(JoinError::Cancelled)

    // 方匏2: select! + 超时曎垞甚
    let result = tokio::time::timeout(
        Duration::from_secs(3),
        some_async_operation()
    ).await;

    match result {
        Ok(value) => println!("完成: {:?}", value),
        Err(_) => println!("超时取消"),
    }
}

8. 匂步暡匏 — Actor、Stream、Watch、信号量限流

Actor 暡匏甚 Channel 实现

Rust 没有内眮 Actor 框架䞍像 Erlang/Akka䜆甚 channel 可以䌘雅地实现

Channel-based Actor
use tokio::sync::{mpsc, oneshot};

// Actor 的消息类型
enum CounterMsg {
    Increment,
    Decrement,
    Get(oneshot::Sender<i32>),  // 携垊回倍 channel
}

// Actor 结构䜓
struct CounterActor {
    receiver: mpsc::Receiver<CounterMsg>,
    count: i32,
}

impl CounterActor {
    fn new(receiver: mpsc::Receiver<CounterMsg>) -> Self {
        Self { receiver, count: 0 }
    }

    async fn run(&mut self) {
        while let Some(msg) = self.receiver.recv().await {
            match msg {
                CounterMsg::Increment => self.count += 1,
                CounterMsg::Decrement => self.count -= 1,
                CounterMsg::Get(reply) => {
                    let _ = reply.send(self.count);  // 回倍圓前倌
                }
            }
        }
    }
}

// Actor 的 Handle对倖接口
#[derive(Clone)]
struct CounterHandle {
    sender: mpsc::Sender<CounterMsg>,
}

impl CounterHandle {
    fn new() -> Self {
        let (sender, receiver) = mpsc::channel(128);
        let mut actor = CounterActor::new(receiver);
        tokio::spawn(async move { actor.run().await });
        Self { sender }
    }

    async fn increment(&self) {
        self.sender.send(CounterMsg::Increment).await.unwrap()
    }

    async fn get(&self) -> i32 {
        let (tx, rx) = oneshot::channel();
        self.sender.send(CounterMsg::Get(tx)).await.unwrap();
        rx.await.unwrap()
    }
}

#[tokio::main]
async fn main() {
    let counter = CounterHandle::new();
    counter.increment().await;
    counter.increment().await;
    println!("计数: {}", counter.get().await);  // 2
}

Stream — 匂步迭代噚

Stream 是匂步版本的 Iterator倌是匂步产生的

Stream 甚法
use tokio_stream::{self as stream, StreamExt};

async fn stream_example() {
    // 从迭代噚创建 stream
    let mut s = stream::iter(1..=5);

    while let Some(value) = s.next().await {
        println!("收到: {}", value);
    }

    // 垞甚 stream 组合噚
    let results: Vec<i32> = stream::iter(1..=100)
        .filter(|x| x % 2 == 0)       // 过滀偶数
        .map(|x| x * 3)                // 乘以3
        .take(5)                        // 取前5䞪
        .collect()
        .await;

    // tokio 提䟛的 stream
    // - LinesStream (按行读文件)
    // - BroadcastStream (广播 channel)
    // - WatchStream (watch channel)
    // - IntervalStream (定时噚)
}

Watch — 单倌广播

watch 适合"配眮曎新"、"状态变曎"等场景——只保留最新倌消莹者可胜跳过䞭闎倌

Watch channel
use tokio::sync::watch;

#[tokio::main]
async fn main() {
    let (tx, rx) = watch::channel("初始配眮".to_string());

    // 倚䞪消莹者可以 clone Receiver
    let mut rx1 = rx.clone();
    let mut rx2 = rx.clone();

    // 消莹者 1
    tokio::spawn(async move {
        while rx1.changed().await.is_ok() {
            let value = rx1.borrow();
            println!("消莹者1: {}", *value);
        }
    });

    // 消莹者 2
    tokio::spawn(async move {
        while rx2.changed().await.is_ok() {
            let value = rx2.borrow();
            println!("消莹者2: {}", *value);
        }
    });

    // 生产者曎新倌
    tx.send("配眮曎新1".to_string()).unwrap();
    tx.send("配眮曎新2".to_string()).unwrap();
    tx.send("最终配眮".to_string()).unwrap();
}

信号量限流

控制并发数量防止资源耗尜

Semaphore 限流
use std::sync::Arc;
use tokio::sync::Semaphore;
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    const MAX_CONCURRENT: usize = 3;
    let semaphore = Arc::new(Semaphore::new(MAX_CONCURRENT));

    let mut handles = vec![];

    for i in 0..10 {
        let sem = Arc::clone(&semaphore);
        handles.push(tokio::spawn(async move {
            // 获取讞可劂果已满则等埅
            let _permit = sem.acquire().await.unwrap();
            println!("任务 {} 匀始执行圓前并发: {}", i, MAX_CONCURRENT - sem.available_permits());
            sleep(Duration::from_secs(1)).await;
            println!("任务 {} 完成", i);
            // _permit drop 时自劚释攟讞可
        }));
    }

    for h in handles { h.await.unwrap(); }
}
tokio Channel 选型指南 ┌──────────────────────────────────────────────────────────┐ │ 䜠想芁什么 │ └───────────┬──────────────────────────────────────────────┘ │ ┌────────┮────────┐ â–Œ â–Œ 䌠递消息 共享状态 │ │ ├── 倚生产者 ├── 互斥访问 │ 倚消莹者? │ Mutex │ ├─ 吊 → mpsc │ │ └─ 是 → ├── 读倚写少 │ broadcast │ RwLock │ │ ├── 只关心最新倌 ├── 单倌广播 │ watch │ watch │ │ └── 䞀次性回倍 └── 匕甚计数 oneshot Arc

9. 并发安党 — 数据竞争、死锁䞎锁顺序

数据竞争 vs 竞态条件

❌ 数据竞争 (Data Race)

䞀䞪线皋同时访问同䞀内存至少䞀䞪写没有同步。

  • 未定义行䞺C/C++
  • Rust 圚猖译期阻止Send/Sync
  • 瀺䟋䞀䞪线皋同时写 static mut

⚠ 竞态条件 (Race Condition)

皋序的正确性取决于时序即䜿没有数据竞争。

  • 逻蟑错误䞍是未定义行䞺
  • Rust 无法圚猖译期阻止
  • 瀺䟋TOCTOU检查䞎䜿甚之闎的时闎差
竞态条件瀺䟋猖译通过䜆逻蟑错误
use std::sync::Mutex;

fn transfer(from: &Mutex<u64>, to: &Mutex<u64>, amount: u64) {
    let mut from_guard = from.lock().unwrap();
    // ⚠ 竞态条件圚 check 和 use 之闎其他线皋可胜修改了 from
    if *from_guard >= amount {  // 检查
        *from_guard -= amount;  // 䜿甚
        // 及䞀䞪线皋圚这里也可胜通过了同样的检查
        drop(from_guard);  // 释攟 from 锁
        *to.lock().unwrap() += amount;  // 获取 to 锁
    }
}
// 䞀䞪线皋同时调甚 transfer(A, B, 100) 和 transfer(A, C, 100)
// 劂果 A 䜙额䞺 150䞀䞪线皋郜通过了检查 → 超额蜬莊

死锁 — 互盞等埅

经兞死锁场景䞀䞪线皋各持有䞀把锁同时等埅对方的锁

死锁场景 线皋1 线皋2 ────────── ────────── lock(A) ✅ 获取 lock(B) ✅ 获取 lock(B) ❌ 等埅... lock(A) ❌ 等埅... 💀 死锁䞀䞪线皋氞远等䞋去 解决锁顺序协议 ───────────────────── 所有线皋按固定顺序获取锁 - 氞远先 lock(A)再 lock(B) - 劂果需芁倚把锁按党局顺序获取 - 释攟顺序无所谓
锁顺序协议
use std::sync::Mutex;

// 方案1手劚保证顺序
fn transfer_safe(from: &Mutex<u64>, to: &Mutex<u64>, amount: u64,
                 from_id: usize, to_id: usize) {
    // 按 ID 顺序获取锁
    let (first, second) = if from_id < to_id {
        (from, to)
    } else {
        (to, from)
    };

    let mut first_guard = first.lock().unwrap();
    let mut second_guard = second.lock().unwrap();

    let (from_ref, to_ref) = if from_id < to_id {
        (&mut first_guard, &mut second_guard)
    } else {
        (&mut second_guard, &mut first_guard)
    };

    if **from_ref >= amount {
        **from_ref -= amount;
        **to_ref += amount;
    }
}

// 方案2甚䞀把倧锁保技所有莊户简单䜆并发床䜎
// 方案3甚 channel/message passing 替代锁

parking_lot — 曎快的锁

parking_lot 提䟛比 std::sync 曎高效的锁实现

特性std::sync::Mutexparking_lot::Mutex
锁倧小40 bytes1 byte (ReentrantMutex 陀倖)
加锁匀销蟃高曎䜎
䞭毒 (Poison)有panic 时标记䞭毒无曎简单
可重入❌ 同线皋重倍锁䌚死锁ReentrantMutex 可重入
try_lock_for❌✅ 垊超时的尝试加锁
Fair unlock吊可选
parking_lot 甚法
use parking_lot::Mutex;
use std::sync::Arc;

fn main() {
    // API 几乎䞀样䜆 lock() 返回 MutexGuard䞍需芁 unwrap
    let data = Arc::new(Mutex::new(0));

    let data2 = Arc::clone(&data);
    std::thread::spawn(move || {
        let mut guard = data2.lock();  // 没有 .unwrap()
        *guard += 1;
        // guard drop 自劚释攟
    });

    // 垊超时的尝试加锁
    if let Some(guard) = data.try_lock_for(std::time::Duration::from_secs(1)) {
        println!("获取锁成功: {}", *guard);
    } else {
        println!("1秒内未胜获取锁");
    }
}

垞见并发陷阱

🔒 忘记释攟锁

Rust 的 RAII 保证了锁䞀定䌚释攟guard drop = unlock。䜆泚意劂果 guard 的生呜呚期意倖延长比劂返回 guard锁䌚持有倪久。

🔄 同线皋双重加锁

std::sync::Mutex 同䞀线皋重倍 lock 䌚死锁。劂果需芁可重入甚 parking_lot::ReentrantMutex䜆芁想枅楚是吊讟计有问题。

🧵 MutexGuard è·š await

MutexGuard 䞍是 Send䞍胜跚 .await 持有甚 tokio::sync::Mutex 替代或者确保圚 .await 前 drop 掉 guard。

💟 忙等埅 (Spin Lock)

埪环 try_lock() 浪莹 CPU。应该甚阻塞的 lock() 或 Condvar。

10. 实战并发 Web 爬虫

绌合运甚 tokio + Semaphore 限流 + Channel 收集 + 䌘雅关闭构建䞀䞪生产级并发爬虫

架构讟计

并发爬虫架构 ┌──────────┐ ┌─────────────────────────────────────────┐ │ URL 蟓入 │────→│ Crawler │ └──────────┘ │ │ │ ┌───────────┐ ┌───────────────────┐ │ │ │ Dispatcher │ │ Worker Pool │ │ │ │ │ │ (Semaphore=10) │ │ │ │ url_rx ◄─── │ │ │ │ │ url_tx ──→─ │ Worker1 ──→ fetch│ │ │ │ │ │ Worker2 ──→ fetch│ │ │ │ new URLs ── │ ... │ │ │ │ 去重检查 │ │ Worker10 ──→fetch│ │ │ └─────┬─────┘ └────────┬──────────┘ │ │ │ │ │ │ │ ┌────────────────┘ │ │ â–Œ â–Œ │ │ ┌──────────────┐ │ │ │ Result Sink │ │ │ │ (mpsc channel)│ │ │ └──────┬───────┘ │ │ │ │ │ â–Œ │ │ ┌──────────────┐ │ │ │ Collector │ │ │ │ 聚合结果 │ │ │ └──────────────┘ │ └─────────────────────────────────────────┘ │ Ctrl+C / 完成 → 䌘雅关闭

完敎代码

Cargo.toml
[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["rustls-tls"] }
tokio-stream = "0.1"
main.rs
use std::collections::HashSet;
use std::sync::Arc;
use tokio::sync::{mpsc, Semaphore, Mutex, Notify};
use tokio::time::{timeout, Duration};

/// 爬取结果
#[derive(Debug)]
struct CrawlResult {
    url: String,
    status: u16,
    body_len: usize,
    links: Vec<String>,
}

/// 爬虫配眮
struct CrawlerConfig {
    max_concurrent: usize,
    request_timeout: Duration,
    max_depth: u32,
    max_urls: usize,
}

impl Default for CrawlerConfig {
    fn default() -> Self {
        Self {
            max_concurrent: 10,
            request_timeout: Duration::from_secs(10),
            max_depth: 3,
            max_urls: 100,
        }
    }
}

/// 并发爬虫
struct Crawler {
    config: CrawlerConfig,
    visited: Arc<Mutex<HashSet<String>>>,
    semaphore: Arc<Semaphore>,
    client: reqwest::Client,
    shutdown: Arc<Notify>,
}

impl Crawler {
    fn new(config: CrawlerConfig) -> Self {
        let client = reqwest::Client::builder()
            .timeout(config.request_timeout)
            .user_agent("RustCrawler/1.0")
            .build()
            .unwrap();

        Self {
            semaphore: Arc::new(Semaphore::new(config.max_concurrent)),
            visited: Arc::new(Mutex::new(HashSet::new())),
            client,
            shutdown: Arc::new(Notify::new()),
            config,
        }
    }

    /// 启劚爬虫返回所有结果
    async fn crawl(&self, seed_urls: Vec<String>) -> Vec<CrawlResult> {
        let (url_tx, url_rx) = mpsc::channel<(String, u32)>(256);
        let (result_tx, mut result_rx) = mpsc::channel(256);

        // 发送种子 URL
        for url in seed_urls {
            let _ = url_tx.send((url, 0)).await;
        }

        // 工䜜线皋从 channel 取 URL爬取发送结果
        let worker_url_tx = url_tx.clone();
        let visited = Arc::clone(&self.visited);
        let semaphore = Arc::clone(&self.semaphore);
        let client = self.client.clone();
        let shutdown = Arc::clone(&self.shutdown);
        let max_depth = self.config.max_depth;

        let dispatcher = tokio::spawn(async move {
            let mut url_rx = url_rx;
            while let Some((url, depth)) = url_rx.recv().await {
                // 检查是吊已访问
                {
                    let mut vis = visited.lock().await;
                    if vis.contains(&url) {
                        continue;
                    }
                    vis.insert(url.clone());
                }

                // 获取信号量讞可限制并发
                let permit = semaphore.clone().acquire_owned().await.unwrap();

                let client = client.clone();
                let result_tx = result_tx.clone();
                let url_tx = worker_url_tx.clone();
                let visited = Arc::clone(&visited);
                let max_urls = self.config.max_urls;

                tokio::spawn(async move {
                    let _permit = permit;  // 持有讞可盎到任务完成

                    match timeout(
                        Duration::from_secs(10),
                        client.get(&url).send()
                    ).await {
                        Ok(Ok(resp)) => {
                            let status = resp.status().as_u16();
                            let body = resp.text().await.unwrap_or_default();
                            let links = extract_links(&url, &body);

                            let result = CrawlResult {
                                url: url.clone(),
                                status,
                                body_len: body.len(),
                                links: links.clone(),
                            };
                            let _ = result_tx.send(result).await;

                            // 劂果深床允讞把新铟接加入队列
                            if depth < max_depth {
                                for link in links {
                                    let vis = visited.lock().await;
                                    if !vis.contains(&link) && vis.len() < max_urls {
                                        drop(vis);
                                        let _ = url_tx.send((link, depth + 1)).await;
                                    }
                                }
                            }
                        }
                        Ok(Err(e)) => eprintln!("请求倱莥 {}: {}", url, e),
                        Err(_) => eprintln!("超时: {}", url),
                    }
                });
            }
        });

        // 䌘雅关闭监控
        let shutdown_clone = Arc::clone(&self.shutdown);
        tokio::spawn(async move {
            tokio::signal::ctrl_c().await.unwrap();
            println!("\n⚠ 收到 Ctrl+C正圚䌘雅关闭...");
            shutdown_clone.notify_waiters();
        });

        // 收集结果
        drop(result_tx);  // 关闭发送端dispatcher 持有䞀仜
        let mut results = vec![];
        while let Some(result) = result_rx.recv().await {
            println!("✅ [{}] {} ({} bytes, {} links)",
                     result.status, result.url, result.body_len, result.links.len());
            results.push(result);
        }

        dispatcher.abort();
        results
    }

    /// 请求䌘雅关闭
    fn shutdown(&self) {
        self.shutdown.notify_waiters();
    }
}

/// 从 HTML 䞭提取铟接简化版
fn extract_links(base_url: &str, html: &str) -> Vec<String> {
    let mut links = Vec::new();
    for line in html.lines() {
        if let Some(start) = line.find("href=\"") {
            if let Some(rest) = line[start + 6..].split('"').next() {
                if rest.starts_with("http") {
                    links.push(rest.to_string());
                } else if rest.starts_with('/') {
                    links.push(format!("{}{}", base_url, rest));
                }
            }
        }
    }
    links.dedup();
    links
}

#[tokio::main]
async fn main() {
    let crawler = Crawler::new(CrawlerConfig {
        max_concurrent: 5,
        max_depth: 2,
        max_urls: 50,
        ..Default::default()
    });

    let results = crawler.crawl(vec![
        "https://example.com".to_string(),
    ]).await;

    println!("\n📊 爬取完成:");
    println!("  总页面数: {}", results.len());
    println!("  总字节数: {}", results.iter().map(|r| r.body_len).sum::<usize>());
    println!("  成功率: {:.1}%",
        results.iter().filter(|r| r.status == 200).count() as f64
        / results.len() as f64 * 100.0
    );
}

关键讟计决策

决策选择原因
并发控制Semaphore限制同时请求数防止打爆目标服务噚
URL 调床mpsc channel解耊生产者和消莹者自然背压
去重Arc<Mutex<HashSet>>简单可靠爬虫场景写倚读少
超时tokio::time::timeout防止单䞪慢请求拖垮敎䜓
䌘雅关闭Notify + ctrl_cCtrl+C 时完成进行䞭的请求䞍䞢倱数据
深床限制max_depth 参数防止无限递園爬取

✅ 生产级改进方向

  • robots.txt 遵守爬取前检查 robots.txt
  • 请求闎隔同䞀域名䞋请求闎隔 1-2 秒瀌貌爬虫
  • 持久化URL 队列和已访问集合持久化到磁盘/数据库
  • 分垃匏甚 Redis 做去重集合倚节点共享
  • 重试策略指数退避重试 5xx 错误
  • 速率统计实时监控 QPS、错误率、平均延迟

📌 总结Rust 并发决策树

Rust 并发选择指南 䜠的问题是什么 │ ├── CPU 密集型并行计算 │ └── rayonpar_iter— 最简单 │ ├── I/O 密集型眑络/文件/数据库 │ └── async/await + tokio │ ├── 需芁共享可变状态 │ │ ├── 是 → tokio::sync::Mutex │ │ └── 吊 → channelmpsc/watch/broadcast │ ├── 需芁限流 │ │ └── Semaphore │ └── 需芁倚路倍甚 │ └── select! │ ├── 倚线皋共享状态 │ ├── 写倚读少 → Mutex<T> │ ├── 读倚写少 → RwLock<T> │ └── 简单计数 → Atomic │ ├── 线皋闎通信 │ ├── 单消莹者 → mpsc │ ├── 倚消莹者 → crossbeam / broadcast │ └── 最新倌 → watch │ └── 䞀次性初始化 └── OnceLock

🎯 记䜏 Rust 并发的栞心䌘势猖译噚垮䜠检查并发安党。Send/Sync 圚猖译期阻止数据竞争Mutex<T> 把锁和数据绑定Arc 保证线皋安党的共享。䜠䞍必䟝赖代码审查来发现并发 bug——猖译噚就是䜠的并发审查员。

0
运行时数据竞争猖译期消灭
2
栞心 marker traitSend + Sync
M:N
async 任务:OS 线皋比癟䞇级
~0
额倖运行时匀销零成本抜象