个人网站架设,养老做增减的网站,宝安附近做网站公司,企业网站目的喜欢的话别忘了点赞、收藏加关注哦#xff08;加关注即可阅读全文#xff09;#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵#xff01;(#xff65;ω#xff65;)
16.3.1. 使用共享来实现并发
还记得Go语言有一句名言是这么说的#xff1a;Do not commun…喜欢的话别忘了点赞、收藏加关注哦加关注即可阅读全文对接下来的教程有兴趣的可以关注专栏。谢谢喵(ω)
16.3.1. 使用共享来实现并发
还记得Go语言有一句名言是这么说的Do not communicate by sharing memory; instead, share memory by communicating.不要用共享内存来通信要用通信来共享内存
上一篇文章就是使用通信的方式来实现并发的。这一篇文章讲一下如何使用共享内存的方式来实现并发。Go语言不建议使用这种方式,Rust支持通过共享状态来实现并发。
上一篇文章讲的Channel类似单所有权一旦值的所有权转移至Channel就无法使用它了。共享内存并发类似于多所有权多个线程可以同时访问同一块内存。
16.3.2. 使用Mutex来只允许一个线程来访问数据
Mutex是mutual exclusion(互斥锁)的简写。
在同一时刻Mutex只允许一个线程来访问某些数据。
想要访问数据线程必须首先获取互斥锁(lock)在Rust里就是调用lock方法获得。lock数据结构是Mutex的一部分它能跟踪谁对数据拥有独占访问权。Mutex通常被描述为通过锁定系统来保护它所持有的数据。
16.3.3. Mutex的两条规则
在使用数据之前必须尝试获取锁(lock)。使用完Mutex所保护的数据必须对数据进行解锁以便其他线程可以获取锁。
16.3.4. MutexT的API
通过Mutex::new函数来创建MutexT其参数就是要保护的数据。MutexT实际上是一个智能指针。
在访问数据前通过lock方法来获取锁,这个方法会阻塞当前线程的运行。lock方法也可能会失败所以返回的值被Result包裹如果成功其值Ok变体附带的值的类型就为MutexGuard智能指针实现了Deref和Drop。
看个例子
use std::sync::Mutex;fn main() {let m Mutex::new(5);{let mut num m.lock().unwrap();*num 6;}println!(m {m:?});
}使用Mutex::new创建了一个互斥锁其保护的数据是5赋给m。所以m的类型是MutexGuardi32。后面使用{}创建了新的小作用域在小作用域里使用lock方法获取值使用unwrap进行错误处理。由于MutexGuard实现了Deref trait我们就可以获得内部数据的引用。所以num是一个可变引用。在小作用域内还使用了解引用*来修改数据的值为6。由于MutexGuard实现了Drop trait所以在小作用域结束后会自动解锁。最后打印了修改后的互斥锁内的内容。
输出
m Mutex { data: 6 }16.3.5. 多线程共享MutexT
看个例子
use std::sync::Mutex;
use std::thread;fn main() {let counter Mutex::new(0);let mut handles vec![];for _ in 0..10 {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());
}counter实际上就是一个计数器只是使用了Mutex包裹以更好地在多线程中调用刚开始的值是0handle目前是一个空Vector下面通过从0到10不包括10的循环创建了10个线程把每个线程得到的handle放到空集合handles里。在线程的闭包里我们的意图是把counter这个互斥锁转移到闭包里所以使用了move关键字然后获取互斥锁然后修改它的值每个线程都加1。当线程执行完后num会离开作用域互斥锁被释放其他线程就可以使用了。从0到10不包括10的循环里还遍历了handles使用join方法这样等每个handle所对应的线程都结束后才会继续执行。最后在主线程里尝试获得counter的互斥锁然后把它打印出来。
输出
$ cargo runCompiling shared-state v0.1.0 (file:///projects/shared-state)
error[E0382]: borrow of moved value: counter-- src/main.rs:21:29|
5 | let counter Mutex::new(0);| ------- move occurs because counter has type Mutexi32, which does not implement the Copy trait
...
8 | for _ in 0..10 {| -------------- inside of this loop
9 | let handle thread::spawn(move || {| ------- value moved into closure here, in previous iteration of loop
...
21 | println!(Result: {}, *counter.lock().unwrap());| ^^^^^^^ value borrowed here after move|
help: consider moving the expression out of the loop so it is only moved once|
8 ~ let mut value counter.lock();
9 ~ for _ in 0..10 {
10 | let handle thread::spawn(move || {
11 ~ let mut num value.unwrap();|For more information about this error, try rustc --explain E0382.
error: could not compile shared-state (bin shared-state) due to 1 previous error错误是在前一次循环中已经把所有权移到前一次的那个线程里了而这一次循环就没发再获得所有权了。
那么如何把counter放到多个线程也就是让多个线程拥有它的所有权呢
16.3.6. 多线程的多重所有权
在15章讲了一个多重所有权的智能指针叫RcT,把counter用Rc包裹即可
let counter Rc::new(Mutex::new(0));在循环里需要把克隆传进线程这里用了类型遮蔽把新counter值设为旧counter的引用
let counter Rc::clone(counter);修改后的代码(记得在使用前引入Rc)
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]: RcMutexi32 cannot be sent between threads safely-- src/main.rs:11:36|
11 | let handle thread::spawn(move || {| ------------- ^------| | || ______________________|_____________within this {closuresrc/main.rs:11:36: 11:43}| | || | required by a bound introduced by this call
12 | | let mut num counter.lock().unwrap();
13 | |
14 | | *num 1;
15 | | });| |_________^ RcMutexi32 cannot be sent between threads safely| help: within {closuresrc/main.rs:11:36: 11:43}, the trait Send is not implemented for RcMutexi32, which is required by {closuresrc/main.rs:11:36: 11:43}: Send
note: required because its used within this closure-- src/main.rs:11:36|
11 | let handle thread::spawn(move || {| ^^^^^^^
note: required by a bound in spawn-- /rustc/eeb90cda1969383f56a2637cbd3037bdf598841c/library/std/src/thread/mod.rs:688:1For more information about this error, try rustc --explain E0277.
error: could not compile shared-state (bin shared-state) due to 1 previous error看报错信息的这部分RcMutexi32 cannot be sent between threads safelyRcMutexi32不能在线程间安全地传递。编译器也告诉我们了原因the trait Send is not implemented for RcMutexi32RcMutexi32没有实现send trait(下一篇文章会讲到)。只有实现send的类型才能在线程间安全地传递。
其实在第15章讲RcT也说到了它不能用于多线程场景RcT不能安全地跨线程共享。它不能确保计数的更改不会被另一个线程中断。这可能会导致错误的计数进而导致内存泄漏或在我们完成之前删除某个值。我们需要的是一种与RcT完全相同的类型但它以线程安全的方式更改引用计数。
那么多线程应该用什么呢有一个智能指针叫做ArcT可以胜任这个场景。
16.3.7. 使用ArcT来进行原子引用计数
ArcT和RcT类似但是它可以用于并发场景。Arc的A指的是Atomic原子的这意味着它是一个原子引用计数类型原子是另一种并发原语。这里不对ArcT做过于详细的介绍只需要知道原子像原始类型一样工作但可以安全地跨线程共享其余信息详见Rust官方文档。
那么为什么所有的基础类型都不是原子的为什么标准库不默认使用ArcT?这是因为
ArcT的功能需要以性能作为代价ArcT和RcT的API都是相同的
既然ArcT和RcT的API都是相同的那么先前的代码就很好改了(记得在使用前引入Arc)
use std::sync::{Arc, Mutex};
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());
}16.3.8. RefCellT/RcT vs. MutexT/ArcT
MutexT提供了内部可变性和Cell家族一样。我们一般使用RefCellT包裹RcT以获得一个有内部可变性的共享所有权数据类型。同样的使用MutexT可以改变ArcT里面的内容。
当使用MutexT时Rust 无法保护您免受各种逻辑错误的影响。使用RcT会带来创建引用循环的风险其中两个RcT值相互引用从而导致内存泄漏。同样 MutexT也存在产生死锁(deadlock) 的风险。当一个操作需要锁定两个资源并且两个线程各自获取其中一个锁导致它们永远等待对方时就会发生这种情况。MutexT和MutexGuard的标准库API文档提供了有用的信息。详见MutexTAPI文档和MutexGuardAPI文档。