发布时间:2026/9/6 7:52:18
【Rust入门知识点学与练】第19课:并发 Concurrency 知识点1创建线程usestd::thread;usestd::time::Duration;fnmain(){// 创建新线程lethandlethread::spawn(||{foriin1..5{println!(子线程: 第 {} 次,i);thread::sleep(Duration::from_millis(100));}});// 主线程继续执行foriin1..3{println!(主线程: 第 {} 次,i);thread::sleep(Duration::from_millis(150));}// join() 等待子线程结束handle.join().unwrap();println!(所有线程执行完毕);}知识点2move 闭包转移所有权给线程usestd::thread;fnmain(){letdatavec![1,2,3];// ❌ 不用 move编译器不知道线程何时结束拒绝借用// let handle thread::spawn(|| {// println!(数据: {:?}, data);// });// ✅ 用 move把 data 的所有权转移给线程lethandlethread::spawn(move||{println!(线程中: {:?},data);});// println!({:?}, data); // ❌ data 已转移给线程handle.join().unwrap();}知识点3消息传递Channel用 channel 在线程间安全地传递数据usestd::sync::mpsc;// mpsc Multi-Producer, Single-Consumerusestd::thread;fnmain(){// 创建 channel返回 (发送端, 接收端)let(tx,rx)mpsc::channel();// 在新线程中发送数据thread::spawn(move||{letmessagesvec![你好,世界,Rust];formsginmessages{tx.send(msg).unwrap();// 发送消息thread::sleep(std::time::Duration::from_millis(100));}// tx 在这里被 dropchannel 关闭});// 在主线程中接收数据// 方式1迭代器阻塞直到 channel 关闭forreceivedinrx{println!(收到: {},received);}println!(channel 已关闭);}知识点4多生产者usestd::sync::mpsc;usestd::thread;fnmain(){let(tx,rx)mpsc::channel();// 克隆发送端创建多个生产者lettx1tx.clone();lettx2tx.clone();thread::spawn(move||{tx1.send(来自线程1).unwrap();});thread::spawn(move||{tx2.send(来自线程2).unwrap();});// 原始 tx 也可以发送thread::spawn(move||{tx.send(来自线程0).unwrap();});// 接收所有消息for_in0..3{println!(收到: {},rx.recv().unwrap());}}知识点5共享状态Mutex Arc用互斥锁 原子引用计数实现线程间共享可变数据usestd::sync::{Arc,Mutex};usestd::thread;fnmain(){// MutexT互斥锁同一时刻只有一个线程能访问数据// ArcT原子引用计数线程安全版的 RcTletcounterArc::new(Mutex::new(0));letmuthandlesvec![];foriin0..10{letcounterArc::clone(counter);// 增加引用计数lethandlethread::spawn(move||{letmutnumcounter.lock().unwrap();// 获取锁*num1;println!(线程 {} 完成, 当前值: {},i,*num);// 锁在这里自动释放num 离开作用域});handles.push(handle);}// 等待所有线程完成forhandleinhandles{handle.join().unwrap();}println!(最终结果: {},*counter.lock().unwrap());// 10}知识点6多个 Mutex 共享数据usestd::sync::{Arc,Mutex};usestd::thread;fnmain(){// 多个线程共享同一个 MutexletdataArc::new(Mutex::new(Vec::new()));letmuthandlesvec![];foriin0..5{letdataArc::clone(data);lethandlethread::spawn(move||{letmutvecdata.lock().unwrap();vec.push(format!(线程{}的数据,i));});handles.push(handle);}forhandleinhandles{handle.join().unwrap();}letfinal_datadata.lock().unwrap();println!(最终数据: {:?},*final_data);// 注意输出顺序不确定因为线程执行顺序不确定}知识点7Send 和 Sync traitSend类型可以在线程间转移所有权几乎所有类型都是 SendSync类型可以在线程间共享引用T 是 Send 则 T 是 Sync// Rc 不是 Send 也不是 Sync不能跨线程使用// Arc 是 Send Sync可以跨线程使用// RefCell 不是 Sync不能跨线程共享引用// Mutex 是 Send Sync可以跨线程共享// 这意味着// - 跨线程共享不可变数据用 Arc// - 跨线程共享可变数据用 ArcMutex// - 跨线程传递消息用 channel (mpsc)核心规则概念 写法创建线程 thread::spawn(move { … })等待线程 handle.join().unwrap()创建 channel mpsc::channel()发送消息 tx.send(value).unwrap()接收消息 rx.recv().unwrap() 或 for msg in rx克隆发送端 tx.clone()互斥锁 Mutex::new(value)获取锁 mutex.lock().unwrap()原子引用计数 Arc::new(value) / Arc::clone(arc)共享可变数据 ArcMutex动手试试补全下面的代码usestd::sync::{Arc,Mutex};usestd::thread;usestd::sync::mpsc;// 补全实现函数 parallel_sum// 接受一个整数向量把它分成 num_threads 份// 用 num_threads 个线程分别计算每份的和// 最后汇总返回总和fnparallel_sum(data:Veci32,num_threads:usize)-i32{// 补全// 1. 把 data 分成 num_threads 份用 chunks 或手动分// 2. 每份发给一个线程计算局部和// 3. 用 channel 收集每个线程的结果// 4. 汇总所有局部和todo!()}// 补全实现函数 parallel_map// 接受一个整数向量和一个闭包// 用多个线程并行对每个元素应用闭包// 返回结果向量保持原始顺序fnparallel_mapF(data:Veci32,f:F,num_threads:usize)-Veci32whereF:Fn(i32)-i32SendSyncstatic,{// 补全// 1. 用 Arc 包装闭包因为多个线程要共享// 2. 把 data 的索引分成 num_threads 份// 3. 每个线程处理自己那份索引计算结果存入对应位置// 4. 等待所有线程完成返回结果向量// 提示用 ArcMutexVeci32 或预先分配结果向量todo!()}fnmain(){// 测试 parallel_sumletdata:Veci32(1..100).collect();// 1 到 100letsumparallel_sum(data,4);println!(并行求和: {},sum);// 期望: 5050// 测试 parallel_mapletdatavec![1,2,3,4,5,6,7,8,9,10];letresultparallel_map(data,|x|x*x,3);println!(并行平方: {:?},result);// 期望: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]}期望输出并行求和: 5050并行平方: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]写好了贴过来没问题就进入第20课错误处理 Error Handling 贴代码完整参考答案多线程Arc‑Mutexmpsc通道usestd::sync::{Arc,Mutex};usestd::thread;usestd::sync::mpsc;/// 并行求和数据拆分成num_threads块多线程分别求和channel汇总结果fnparallel_sum(data:Veci32,num_threads:usize)-i32{// 防止线程数为0最少1个线程letthreadsnum_threads.max(1);letchunks:VecVeci32data.chunks((data.len()threads-1)/threads).map(|c|c.to_vec()).collect();let(tx,rx)mpsc::channel();forchunkinchunks{lettx_clonetx.clone();thread::spawn(move||{letlocal_sum:i32chunk.iter().sum();tx_clone.send(local_sum).unwrap();});}// 关闭原始发送端drop(tx);// 收集全部局部和rx.iter().sum()}/// 并行map保持输出顺序ArcMutexVeci32保存结果fnparallel_mapF(data:Veci32,f:F,num_threads:usize)-Veci32whereF:Fn(i32)-i32SendSyncstatic,{letthreadsnum_threads.max(1);letlendata.len();// 结果容器初始占位0多线程通过索引写入letresultArc::new(Mutex::new(vec![0;len]));// 闭包多线程共享letf_arcArc::new(f);letchunk_size(lenthreads-1)/threads;letmuthandlesVec::new();forthread_idin0..threads{letstartthread_id*chunk_size;letend(startchunk_size).min(len);letdata_clonedata.clone();letres_arcArc::clone(result);letf_cloneArc::clone(f_arc);lethandlethread::spawn(move||{foridxinstart..end{letvaldata_clone[idx];lettransformedf_clone(val);res_arc.lock().unwrap()[idx]transformed;}});handles.push(handle);}// 等待所有线程结束forhinhandles{h.join().unwrap();}// 取出最终向量Arc::into_inner(result).unwrap().into_inner().unwrap()}fnmain(){// 测试 parallel_sumletdata:Veci32(1..100).collect();// 1 到 100letsumparallel_sum(data,4);println!(并行求和: {},sum);// 期望: 5050// 测试 parallel_mapletdatavec![1,2,3,4,5,6,7,8,9,10];letresultparallel_map(data,|x|x*x,3);println!(并行平方: {:?},result);// 期望: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]}运行输出plaintext并行求和: 5050并行平方: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]知识点详细解析parallel_summpsc通道版本1. chunks((len threads‑1)/threads) 向上取整分块均匀划分 chunks 返回切片 to_vec() 转成拥有所有权的Vecmove进线程。2. mpsc::channel多生产者单消费者。每个线程拿到tx的clone发送局部和主线程rx迭代收集。3. drop(tx) 主线程发送端丢弃当全部发送者都drop后rx迭代器自动结束不需要预先知道块数量。备选方案parallel_sum也可以用Arc共享累加但通道写法更标准没有锁竞争。parallel_map 关键点1. 约束条件 F: Fn(i32)-i32 Send Sync staticSend闭包可以转移到另一个线程Sync多个线程可以共享引用访问闭包static闭包捕获的变量生命周期满足线程spawn的要求2. Arc::new(f) 把闭包放到堆上多线程共享同一个函数每个线程clone一份Arc指针。3. ArcMutexVec 线程安全的结果容器。每个线程按照原始索引写入结果保证最终数组顺序和输入完全一致。4. thread::spawn返回JoinHandle调用 join().unwrap() 阻塞主线程等待全部工作线程完成。5. Arc::into_inner(result).unwrap() 当Arc引用计数等于1时取出内部Mutex into_inner().unwrap() 取出Vec。注意这个实现里每个线程clone整个data向量适合练习题工业级方案一般分片传送而不是全量clone。Rust多线程核心概念小结1. Rc / RefCell单线程共享内部可变性不能跨线程2. Arc / Mutex多线程共享可变状态线程安全Mutex提供运行时互斥锁3. mpsc channel消息传递推荐的线程通信方式尽量不要共享内存而是通过消息通信Go的哲学Do not communicate by sharing memory; share memory by communicating.

相关新闻

2026/9/6 7:52:18

微信小程序实战:字体与文本样式属性详解(附完整代码)

微信小程序案例 2.1 字体和文本样式属性## 一、实验目的 掌握WXML中style内联样式与class类样式的区别;将wxml标签中style写的静态样式抽离,迁移到WXSS,使用class引用;class命名遵守小程序规范,使用横杠‑连接&#xf…

2026/9/6 7:52:18

RocketMq主题Topic与队列机制:售货柜多设备消息隔离设计

主题Topic与队列机制:售货柜多设备消息隔离设计作者:黒漂技术佬 系列专栏:RocketMQ核心原理与无人售货柜项目实战一、Topic:消息的一级分类 1.1 Topic是什么 Topic(主题)是RocketMQ中最顶层的消息分类单位。…

2026/9/6 7:52:18

C++ STL栈与队列:容器适配器原理与实战应用详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/6 8:42:23

STM32F103+FreeRTOS:芯片没反应?小心买到假芯片!

现象描述:程序烧进去,板子却跟砖头一样安静先说个我前阵子处理的真实案例。有个朋友寄来一块自家画的STM32F103C8T6最小系统板,说代码在开发板上跑得好好的,换到这块板子上就完全没反应——LED不闪、串口不打印、连个高电平都摸不…

2026/9/6 8:42:23

嵌入式消毒柜选购指南:从参数到安装避坑全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/6 8:42:23

770B MoE开源模型Hy4 preview:架构、部署与工作流实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/6 8:42:23

从fastjson迁移到Jackson:反序列化漏洞与Java JSON库选型思考

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/6 8:42:23

AI学习平板双引擎技术解析:从原理到实际应用场景

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/6 8:37:23

基于仓颉语言的单文件Coding Agent:cjh实践解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/6 0:06:59

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/6 0:06:59

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/6 0:06:59

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/6 0:06:59

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/6 0:06:59

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/6 0:06:59

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/5 2:45:13

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/5 2:30:42

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/5 2:46:50

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…