首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Rust专项——并发前置:Send/Sync 与共享数据设计

Rust专项——并发前置:Send/Sync 与共享数据设计

作者头像
红目香薰
发布2025-12-16 16:22:14
发布2025-12-16 16:22:14
1290
举报
文章被收录于专栏:CSDNToQQCodeCSDNToQQCode

本篇作为进入并发编程之前的地基,讲清楚两个关键自动 trait:SendSync,以及在多线程下如何安全地共享和修改数据。包含 ArcMutexRwLock、消息通道(channel)与常见陷阱。

1. Send 与 Sync 是什么?

  • Send:类型的所有权可以在线程间转移(move)。绝大多数标准类型都是 Send
  • Sync:如果 T: Sync,则 &T 可以在多个线程间共享(即 &TSend)。
  • 直觉:Send 关乎“能否把值交给另一个线程”;Sync 关乎“能否把不可变引用同时给多个线程用”。

常见:

  • i32, String, Vec<T>Send
  • &T:当且仅当 T: Sync 时,&T: Send
  • Rc<T>:非线程安全(不是 Send/Sync);Arc<T>:线程安全引用计数。
  • Cell<T>/RefCell<T>:非 Sync,仅单线程;Mutex<T>/RwLock<T>Sync,用于并发。

2. 在线程间共享所有权:Arc

代码语言:javascript
复制
use std::sync::Arc;
use std::thread;

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

    for _ in 0..4 {
        let shared = Arc::clone(&data); // 增加引用计数
        handles.push(thread::spawn(move || {
            // 只读共享:无需加锁
            shared.iter().sum::<i32>()
        }));
    }

    let total: i32 = handles.into_iter().map(|h| h.join().unwrap()).sum();
    println!("sum = {}", total);
}
在这里插入图片描述
在这里插入图片描述

结论:只读共享用 Arc<T> 即可;若需写,则配合锁。

3. 可变共享:Mutex 与 RwLock

  • Mutex<T>:互斥锁,独占写;
  • RwLock<T>:读写锁,多读一写;
  • 两者都需要与 Arc 搭配才能跨线程共享所有权。
代码语言:javascript
复制
use std::sync::{Arc, Mutex};
use std::thread;

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

    for _ in 0..8 {
        let c = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            for _ in 0..100_000 {
                *c.lock().unwrap() += 1; // 临界区
            }
        }));
    }

    for h in handles { h.join().unwrap(); }
    println!("count = {}", *counter.lock().unwrap());
}
在这里插入图片描述
在这里插入图片描述

要点:

  • lock() 返回 MutexGuard<T>,实现了 DerefMut,出作用域自动解锁。
  • 避免把锁守卫长期持有(缩小作用域),减少死锁风险。

RwLock 示例:

代码语言:javascript
复制
use std::sync::{Arc, RwLock};
use std::thread;

fn main() {
    let store = Arc::new(RwLock::new(Vec::<u32>::new()));

    // 写入线程
    {
        let s = Arc::clone(&store);
        std::thread::spawn(move || {
            for i in 0..10 { s.write().unwrap().push(i); }
        });
    }

    // 读取线程
    {
        let s = Arc::clone(&store);
        std::thread::spawn(move || {
            loop {
                let snapshot = s.read().unwrap().clone(); // 短暂持锁,尽快释放
                if snapshot.len() >= 10 { break; }
            }
        });
    }
}

4. 消息传递:无共享更简单

相比共享可变状态,“消息传递”往往更简单更安全:

代码语言:javascript
复制
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
    let (tx, rx) = mpsc::channel::<String>();

    thread::spawn(move || {
        for i in 0..3 {
            tx.send(format!("msg-{i}")).unwrap();
            thread::sleep(Duration::from_millis(50));
        }
    });

    for received in rx { println!("got: {}", received); }
}
在这里插入图片描述
在这里插入图片描述
  • mpsc:多生产者、单消费者。
  • 若需要多消费者,可用跨端库或 broadcast 模式(如 tokio::sync)。

5. Rc/RefCell 在多线程中的坑

  • Rc<T>RefCell<T> 并非线程安全:不实现 Send/Sync
  • 多线程请改用 Arc<T>Mutex<T>/RwLock<T>
  • 在单线程异步中(如 GUI)Rc<RefCell<T>> 仍然非常好用,但请清晰限定单线程语境。

6. 组合范式:Arc + Mutex + Channel

  • Arc<Mutex<T>> 共享可变数据;
  • mpsc 通知与任务派发;
  • 线程之间只传消息或轻量句柄,避免传整个大对象。
代码语言:javascript
复制
use std::sync::{Arc, Mutex, mpsc};
use std::thread;

fn main() {
    let state = Arc::new(Mutex::new(0usize));
    let (tx, rx) = mpsc::channel::<usize>();

    // worker
    {
        let st = Arc::clone(&state);
        thread::spawn(move || {
            for job in rx { *st.lock().unwrap() += job; }
        });
    }

    // dispatcher
    for n in [1,2,3,4,5] { tx.send(n).unwrap(); }
    drop(tx); // 关闭通道,结束 worker 循环

    // 等待片刻或 join 线程(示意)
    std::thread::sleep(std::time::Duration::from_millis(10));
    println!("final = {}", *state.lock().unwrap());
}
在这里插入图片描述
在这里插入图片描述

7. 最佳实践清单

  • 优先选择“消息传递”而非“共享可变状态”。
  • 必须共享可变时:Arc<Mutex<T>>Arc<RwLock<T>>
  • 缩小锁的作用域,避免在持锁时执行耗时操作(I/O、sleep)。
  • 尽可能让数据不可变(Arc<T> 读多写少)。
  • 异步运行时(Tokio)环境下,优先使用其提供的异步原语(如 tokio::sync::Mutex)。

8. 练习

  1. Arc<Mutex<Vec<u32>>> 在多个线程累加 0..10_000,验证最终长度与内容。
  2. mpsc 发送 100 条任务,worker 统计字节总数,最后主线程打印总和。
  3. 把一个 Rc<RefCell<T>> 迁移到多线程版本,改成 Arc<Mutex<T>> 并跑通。

至此,并发的核心安全基石已搭好。后续可进入线程池、通道模式、异步运行时(Tokio)、并发数据结构的系统实践。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. Send 与 Sync 是什么?
  • 2. 在线程间共享所有权:Arc
  • 3. 可变共享:Mutex 与 RwLock
  • 4. 消息传递:无共享更简单
  • 5. Rc/RefCell 在多线程中的坑
  • 6. 组合范式:Arc + Mutex + Channel
  • 7. 最佳实践清单
  • 8. 练习
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档