素数|连续 3 年最受欢迎:Rust,香!( 四 )
上例中,我们可以在main函数中通过channel得到了子线程中的对象val。
注意,tx.send(val).unwrap(); 之后,val的所有权已经发生了变化,接下来在子线程中不能再对val进行操作,否则会有编译错误,如下代码:
use std::thread;use std::sync::mpsc;fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from("hi"); tx.send(val).unwrap(); println!("val is {}", val);//在这里会发生编译错误 }); let received = rx.recv().unwrap(); println!("Got: {}", received);}
这里尝试在通过 tx.send 发送 val 到通道中之后将其打印出来。允许这么做是一个坏主意:一旦将值发送到另一个线程后,那个线程可能会在我们再次使用它之前就将其修改或者丢弃。这会由于不一致或不存在的数据而导致错误或意外的结果。对于上面的代码,编译器给出错误:
error[E0382]: use of moved value: `val` --> src/main.rs:10:31 |9 | tx.send(val).unwrap(); | --- value moved here10 | println!("val is {}", val); | ^^^ value used here after move | = note: move occurs because `val` has type `std::string::String`, which doesnot implement the `Copy` trait
我们通过channel能够实现多线程发送共享数据,但是依然有个问题:通道一旦将一个值或者对象send出去之后,我们将无法再使用这个值;如果面对这样一个需求:将一个计数器counter传给10条线程,每条线程对counter加1,最后在main函数中汇总打印出counter的值,这样一个简单的需求如果使用C++或者Golang或者其它非Rust语言实现,非常容易,一个全局变量,一把锁,几行代码轻松搞定,但是Rust语言可就没那么简单,如果你是一个新手,你可能会经历如下“艰难历程”:
首先很自然写出第一版:
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());}
多线程有了,Mutex锁也有了,能保证每一次加一都是原子操作,代码看起来没什么问题,但是编译器会无情报错:
error[E0382]: capture of moved value: `counter` --> src/main.rs:10:27 |9 | let handle = thread::spawn(move || { | ------- value moved (into closure) here10 | let mut num = counter.lock().unwrap(); | ^^^^^^^ value captured here after move | = note: move occurs because `counter` has type `std::sync::Mutex`, which does not implement the `Copy` traiterror[E0382]: use of moved value: `counter` --> src/main.rs:21:29 |9 | let handle = thread::spawn(move || { | ------- value moved (into closure) here...21 | println!("Result: {}", *counter.lock().unwrap()); | ^^^^^^^ value used here after move | = note: move occurs because `counter` has type `std::sync::Mutex`, which does not implement the `Copy` traiterror: aborting due to 2 previous errors
错误信息表明 counter 值的所有权被move了,但是我们又去引用了,根据所有权规则,所有权转移之后不允许访问,但是为什么会发生?
让我们简化程序来进行分析。不同于在 for 循环中创建 10 个线程,仅仅创建两个线程来观察发生了什么。将示例中第一个 for 循环替换为如下代码:
let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1;});handles.push(handle);let handle2 = thread::spawn(move || { let mut num2 = counter.lock().unwrap(); *num2 += 1;});handles.push(handle2);
这里创建了两个线程并将用于第二个线程的变量名改为 handle2 和 num2,编译会给出如下错误:
error[E0382]: capture of moved value: `counter` --> src/main.rs:16:24 |8 | let handle = thread::spawn(move || { | ------- value moved (into closure) here...16 | let mut num2 = counter.lock().unwrap(); | ^^^^^^^ value captured here after move | = note: move occurs because `counter` has type `std::sync::Mutex`, which does not implement the `Copy` traiterror[E0382]: use of moved value: `counter` --> src/main.rs:26:29 |8 | let handle = thread::spawn(move || { | ------- value moved (into closure) here...26 | println!("Result: {}", *counter.lock().unwrap()); | ^^^^^^^ value used here after move | = note: move occurs because `counter` has type `std::sync::Mutex`, which does not implement the `Copy` traiterror: aborting due to 2 previous errors
啊哈!第一个错误信息中说,counter 所有权被移动进了 handle 所代表线程的闭包中。因此我们无法在第二个线程中再次捕获 counter , Rust 告诉我们不能将 counter 的所有权移动到多个线程中。所以错误原因明朗了,因为我们在循环中创建了多个线程,第一条线程获取了 counter 所有权后,后面的线程再也拿不到 counter 的所有权。如何让多条线程同时间接(注意,只能是间接)拥有一个对象的所有权,哦,对了,引用计数!
推荐阅读
- 波塞冬|游戏王:童年最爱下级白板怪兽魔兽,而帝王海马是海神波塞冬车夫
- 阿根廷单日新增病例数连续三天破万,累计确诊病例达392009例
- 特朗普接受提名演讲连续甩锅中国,外交部:敦促美方不要借大选拿中国说事
- 成团|《少年之名》成团,今年最糊的一档选秀,完全是“回锅肉”大乱炖
- 跑出“加速度” 全国交通固定资产投资连续4个月实现正增长
- 加速度|跑出“加速度” 全国交通固定资产投资连续4个月实现正增长
- 阿根廷新冠肺炎确诊病例连续三天新增过万 社会强制隔离继续延长
- |西海岸新区一男子连续高空抛物 涉嫌以危险方法危害公共安全罪被警方采取刑事强制措施
- 印度新冠病例一天增7.7万,连续三周成全球单日新增病例最多国家
- 综艺|《少年之名》成团,今年最糊的一档选秀,完全是“回锅肉”大乱炖
