我是 Rust 的新手,我正在嘗試將計算作業分配給執行緒。
我有字串向量,我想為每個字串創建一個執行緒來完成他的作業。有簡單的代碼:
use std::thread;
fn child_job(s: &mut String) {
*s = s.to_uppercase();
}
fn main() {
// initialize
let mut thread_handles = vec![];
let mut strings = vec![
"hello".to_string(),
"world".to_string(),
"testing".to_string(),
"good enough".to_string(),
];
// create threads
for s in &mut strings {
thread_handles.push(thread::spawn(|| child_job(s)));
}
// wait for threads
for handle in thread_handles {
handle.join().unwrap();
}
// print result
for s in strings {
println!("{}", s);
}
}
編譯時出現錯誤:
error[E0597]: `strings` does not live long enough
--> src/main.rs:18:14
|
18 | for s in &mut strings {
| ^^^^^^^^^^^^
| |
| borrowed value does not live long enough
| argument requires that `strings` is borrowed for `'static`
...
31 | }
| - `strings` dropped here while still borrowed
error[E0505]: cannot move out of `strings` because it is borrowed
--> src/main.rs:28:14
|
18 | for s in &mut strings {
| ------------
| |
| borrow of `strings` occurs here
| argument requires that `strings` is borrowed for `'static`
...
28 | for s in strings {
| ^^^^^^^ move out of `strings` occurs here
我不明白指標的生命周期有什么問題以及我應該如何解決這個問題。對我來說它看起來沒問題,因為每個執行緒只獲得一個可變的字串指標,并且不會以任何方式影響向量本身。
uj5u.com熱心網友回復:
使用thread::spawnand JoinHandles,借用檢查器不夠聰明,無法知道您的執行緒將在main退出之前完成(這對借用檢查器有點不公平,它真的不知道),因此它不能證明它strings會存在足夠長的時間讓您的執行緒處理它。您可以像@tedtanner 建議的那樣使用Arcs 來回避該問題(從某種意義上說,這意味著您正在運行時進行生命周期管理),或者您可以使用作用域執行緒。
作用域執行緒本質上是一種告訴借用檢查器的方式:是的,這個執行緒將在作用域結束(被丟棄)之前完成。然后,您可以將對當前執行緒堆疊上的事物的參考傳遞給另一個執行緒:
crossbeam::thread::scope(|scope| {
for s in &mut strings {
scope.spawn(|_| child_job(s));
}
}) // All spawned threads are auto-joined here, no need for join_handles
.unwrap();
操場
現在,你需要一個 crate(我推薦crossbeam),但這個特性最終應該會成為標準。
uj5u.com熱心網友回復:
凱撒的回答顯示了如何使用橫梁的作用域執行緒解決問題。如果您不想依賴橫梁,那么將值包裝在 中的方法(Arc<Mutex<T>>如 tedtanner 的回答所示)是一種合理的一般策略。
但是在這種情況下,互斥鎖實際上是不必要的,因為執行緒不共享字串,無論是彼此還是與主執行緒。鎖定是 using 的產物Arc,它本身是由靜態生命周期規定的,而不是共享的需要。盡管鎖是非競爭的,但它們確實增加了一些開銷,最好避免。在這種情況下,我們可以避免這兩種情況Arc,并Mutex通過將每個字串移動到其各自的執行緒,并在執行緒完成后檢索修改后的字串。
此修改僅使用標準庫和安全代碼編譯和運行,不需要Arcor Mutex:
// ... child_job defined as in the question ...
fn main() {
let strings = vec![
"hello".to_string(),
"world".to_string(),
"testing".to_string(),
"good enough".to_string(),
];
// start the threads, giving them the strings
let mut thread_handles = vec![];
for mut s in strings {
thread_handles.push(thread::spawn(move || {
child_job(&mut s);
s
}));
}
// wait for threads and re-populate `strings`
let strings = thread_handles.into_iter().map(|h| h.join().unwrap());
// print result
for s in strings {
println!("{}", s);
}
}
操場
uj5u.com熱心網友回復:
Rust 不知道你的字串會和你的執行緒一樣長,所以它不會將它們的參考傳遞給執行緒。想象一下,如果您將對字串的參考傳遞給另一個執行緒,那么原始執行緒認為它已使用該字串完成并釋放了它的記憶體。這將導致未定義的行為。Rust 通過要求字串要么保存在參考計數指標后面(確保它們的記憶體在它們仍然在某處參考時不會被釋放)或者它們具有'static生命周期,這意味著它們存盤在可執行二進制檔案本身中來防止這種情況發生。
此外,Rust 不允許您跨執行緒共享可變參考,因為它是不安全的(多個執行緒可能會嘗試一次更改參考的資料)。您想將std::sync::Arca 與 a 結合使用std::sync::Mutex。您的strings向量將變為Vec<Arc<Mutex<String>>>. 然后,您可以復制Arc(使用.clone())并跨執行緒發送。Arc是一個指標,它保持一個原子遞增的參考計數(閱讀:以執行緒安全的方式)。互斥鎖允許執行緒臨時鎖定字串,以便其他執行緒無法觸摸它,然后稍后解鎖字串(執行緒可以在鎖定時安全地更改字串)。
您的代碼將如下所示:
use std::thread;
use std::sync::{Arc, Mutex};
fn child_job(s: Arc<Mutex<String>>) {
// Lock s so other threads can't touch it. It will get
// unlocked when it goes out of scope of this function.
let mut s = s.lock().unwrap();
*s = s.to_uppercase();
}
fn main() {
// initialize
let mut thread_handles = Vec::new();
let strings = vec![
Arc::new(Mutex::new("hello".to_string())),
Arc::new(Mutex::new("world".to_string())),
Arc::new(Mutex::new("testing".to_string())),
Arc::new(Mutex::new("good enough".to_string())),
];
// create threads
for i in 0..strings.len() {
let s = strings[i].clone();
thread_handles.push(thread::spawn(|| child_job(s)));
}
// wait for threads
for handle in thread_handles {
handle.join().unwrap();
}
// print result
for s in strings {
let s = s.lock().unwrap();
println!("{}", *s);
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/430564.html
上一篇:并行化RandomizedSearchCV以限制使用的CPU數量
下一篇:從另一個執行緒延遲設定滑鼠游標
