素数|连续 3 年最受欢迎:Rust,香!( 五 )
通过使用智能指针 Rc 来创建引用计数的值,尝试使用 Rc 来允许多个线程拥有 Mutex 于是写了第二版:
use std::rc::Rc;use std::sync::Mutex;use std::thread;fn main() { let counter = Rc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Rc::clone(&counter;); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("Result: {}", *counter.lock().unwrap());}
再一次编译并…出现了不同的错误!编译器真是教会了我们很多!
error[E0277]: the trait bound `std::rc::Rc:std::marker::Send` is not satisfied in `[closure@src/main.rs:11:36:15:10counter:std::rc::Rc]` --> src/main.rs:11:22 |11 | let handle = thread::spawn(move || { | ^^^^^^^^^^^^^ `std::rc::Rc`cannot be sent between threads safely | = help: within `[closure@src/main.rs:11:36: 15:10counter:std::rc::Rc]`, the trait `std::marker::Send` isnot implemented for `std::rc::Rc` = note: required because it appears within the type`[closure@src/main.rs:11:36: 15:10counter:std::rc::Rc]` = note: required by `std::thread::spawn`
编译错误信息中有关键的一句:
`std::rc::Rc` cannot be sent between threads safely。
不幸的是,Rc 并不能安全的在线程间共享。当 Rc 管理引用计数时,它必须在每一个 clone 调用时增加计数,并在每一个克隆被丢弃时减少计数。Rc 并没有使用任何并发原语,来确保改变计数的操作不会被其他线程打断。在计数出错时可能会导致诡异的 bug,比如可能会造成内存泄漏,或在使用结束之前就丢弃一个值。我们所需要的是一个完全类似 Rc,又以一种线程安全的方式改变引用计数的类型。所幸 Arc 正是 这么一个类似 Rc 并可以安全的用于并发环境的类型。字母 “a” 代表 原子性(atomic),所以这是一个原子引用计数(atomically reference counted)类型。
于是改写了第三版:
use std::sync::{Mutex, Arc};use std::thread;fn main() { 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 || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("Result: {}", *counter.lock().unwrap());}
这次编译通过,并且打印出了正确的结果,最终,在严厉的编译器的逐步引导,“谆谆教诲”下,我们总算写出了正确的代码。
Rust编译器对多线程数据共享,多线程数据传递这种内存安全事故多发区进行了极其严苛的检查和限制,确保编译时就能发现潜在的内存安全问题。在多线程传递数据时,除了通过channel,你没有第二种选择;在多线程数据共享时,除了Arc+Mutex(如果多线程共享的只是int bool这类简单数据类型,你还可以使用原子操作) ,你同样没有别的选择。虽然 Rust极其缺乏灵活性,但是这同样是它的有点,因为编译器一直在逼着你写出正确的代码,极大减少了程序的维护成本。
以上是我对Rust内存安全保障手段的一些理解,Rust使用一些乍一看很奇怪的特性,非常清晰的定义了一个安全的边界,并在上面做以足够的检查,保证你的代码不会出问题。Rust做到了没有垃圾回收的内存安全,没有数据竞争的并发安全。同时一个新手Rust程序员刚入坑Rust时,大部分的时间都是在解决编译问题。一个新手C++程序员初期可能会写出很多不安全的代码,埋下很多坑,但是新手Rust不会,因为一个新手Rust写出的不安全代码在编译阶段就被拦截了,根本没有机会埋坑,Rust承诺编译通过的Rust程序不会存在内存安全问题(注意:如果通过unsafe关键字强制关闭安全检查,则依然有可能出现内存安全问题)。
三Rust开发效率问题
关于Rust开发效率问题,没有一个统一的客观评价标准,基本靠个人主观感觉而定。每个人对不同语言掌握的熟练度也是影响开发效率的重要因素。关于开发效率,谈一谈个人的感受:先说入门,由于Rust一些奇葩的语法的存在(最麻烦的莫过于生命周期标记),导致Rust入门不像Python和Golang等语言那样轻松,但是因为Rust主要是为了替代C/C++这类系统语言而存在,其借鉴了大量C++的语法,如果对C++熟悉,Rust入门不是难事;其次说说开发速度,对于初学者,Rust开发体验就像在上海开始实行的垃圾分类时上海人民的那种困惑和凌乱,编译器检查太严格了,大多数时间都是在解决编译问题,一种在其它语言中理所当然的写法,在Rust中就是不行,不过好在编译器的提示非常友好,根据编译错误提示大多数时候能够找到答案,不过编译虽然费事,可一旦编译通过,程序员就不需要关心内存安全,内存泄漏等头疼问题,只需要关注于业务逻辑,写了一个多月的Rust,debug次数屈指可数,而且每次debug都是因为业务逻辑,从来没有因为代码内存错误,崩溃等问题debug;如果对Rust稍微熟练一些,其开发速度绝对不会比Python和Golang慢,因为在编译阶段,Rust就解决了大部分的问题,省去了大量的debug时间。
推荐阅读
- 波塞冬|游戏王:童年最爱下级白板怪兽魔兽,而帝王海马是海神波塞冬车夫
- 阿根廷单日新增病例数连续三天破万,累计确诊病例达392009例
- 特朗普接受提名演讲连续甩锅中国,外交部:敦促美方不要借大选拿中国说事
- 成团|《少年之名》成团,今年最糊的一档选秀,完全是“回锅肉”大乱炖
- 跑出“加速度” 全国交通固定资产投资连续4个月实现正增长
- 加速度|跑出“加速度” 全国交通固定资产投资连续4个月实现正增长
- 阿根廷新冠肺炎确诊病例连续三天新增过万 社会强制隔离继续延长
- |西海岸新区一男子连续高空抛物 涉嫌以危险方法危害公共安全罪被警方采取刑事强制措施
- 印度新冠病例一天增7.7万,连续三周成全球单日新增病例最多国家
- 综艺|《少年之名》成团,今年最糊的一档选秀,完全是“回锅肉”大乱炖
