Rust并发编程指南:精通Arc、Mutex与Channel,实现线程安全

征服Rust并发:Arc、Mutex、Channel三大护法,让你的代码从此刀枪不入! 关注梦兽编程微信公众号,幽默学习Rust。 个人网站:rexai.top 你是否曾在深夜,为了一个多线程的bug而捶胸顿足? 想象一个混乱的厨房,几个厨师(线程)同时在做菜。他们抢着用同一把刀,同时去修改同一个菜单,甚至有人刚把菜谱扔进垃圾桶,另一个人就跑过去想照着做菜。结果呢?不是手指被切到,就是菜谱被弄得乱七八糟,最终端上一盘“薛定谔的菜”——味道全凭运气。 这就是许多语言中的并发编程,强大,但也充满了混乱和危险。你得像个操心的老妈子,处处设置路障(比如各种锁),时时提醒自己别犯错,但稍不留神,灾难就会发生。 现在,让我们走进Rust的厨房。 这里的厨房有点不一样。门口站着一位极其严格、甚至有点唠叨的管家(Rust编译器)。他不会让你随便开火,而是会提前把所有可能出错的地方都指出来,冲着你大喊:“嘿!你不能把这把刀给别人,除非你确定自己不用了!” “喂!那个菜单是共享的,一次只能一个人改!” 虽然有点烦,但结果是:绝对安全。你可以在这个厨房里放一百个厨师,他们也能井然有序地合作,绝不会发生事故。 这就是Rust并发编程的魅力:它把“操心”的活儿从你的大脑,转移给了编译器。你只需要理解并遵守管家的几条核心规则,就能写出安全、高效的“傻瓜式”并发代码。 今天,我们就来把这位“管家”的规矩,用大白话给你掰扯清楚。 规矩一:想开分身?先学会“放手” 在Rust里,开启一个新线程,就像创造一个自己的分身去干活。我们用 thread::spawn 这个咒语。 但这位管家(编译器)有个死规矩:你不能把自己的工具(数据)随便借给分身用,万一你这边用完了把工具销毁了,分身那边一用,直接程序崩溃(悬垂指针)。 管家要求你必须明确地“赠予”。用一个 move 关键字,告诉他:“这玩意儿我不要了,全权送给我的分身了!” 看看代码: use std::thread; fn main() { let name = String::from("梦兽编程"); let handle = thread::spawn(move || { println!("我的分身说:你好, {name}"); }); // 下面这行代码如果取消注释,管家会立刻打你手心 // println!("我自己说:你好, {name}"); handle.join().unwrap(); // 等分身干完活 } 看到了吗?一旦你把 name 这个变量 move 给了分身线程,本体就失去了对它的所有权。这就像你把家里的唯一一把钥匙给了室友,你就再也进不了门了。这种看似霸道的规则,从根源上杜绝了“两人同时用一把钥匙开门”的混乱。 规矩二:共享“只读”资料?请用“原子级”图书馆 有时候,我们不希望把东西完全送出去,而是想让很多分身都能“只读”一份共享资料,比如一份全员共享的“烹饪指南”。 直接共享?管家会再次跳出来阻止你。因为他不知道你们谁会先读完,谁会后读完,万一资料的主人(主线程)提前下班把指南烧了怎么办? 这时,我们需要一个神奇的道具:Arc<T>,全称是“原子引用计数”(Atomic Reference Counting)。 别被名字吓到,把它想象成一个“图书馆里的共享阅览室”。 Arc::new(data) 就是把一份资料放进这个阅览室里。每当一个分身想读这份资料,就去办一张阅览证,也就是 Arc::clone(&data)。这个过程非常轻量,只是增加了“正在阅读人数”的计数而已。 当分身读完下班后,他的阅览证就自动作废(计数减一)。直到最后一个读者也离开,阅览室才会关闭,资料才会被销毁。 use std::sync::Arc; use std::thread; fn main() { let cooking_guide = Arc::new(vec!["第一步:洗菜", "第二步:切菜", "第三步:下锅"]); for i in 0..3 { let guide_for_clone = Arc::clone(&cooking_guide); thread::spawn(move || { println!("厨师 {i} 号正在阅读指南: {:?}", guide_for_clone); }); } // 等待一会,让厨师们有时间阅读 thread::sleep(std::time::Duration::from_secs(1)); } 通过 Arc,我们安全地实现了数据的“共享只读”。就像图书馆的规矩,你可以看,可以复印,但绝不能在原件上涂改。 ...

January 15, 2024 · 2 min · 248 words · 梦兽编程