[英]How to start and stop a worker thread
我有以下要求,這是其他編程語言的標准要求,但我不知道如何在 Rust 中執行。
我有一個 class,我想編寫一個方法來生成一個滿足 2 個條件的工作線程:
例如,這是我的虛擬代碼:
struct A {
thread: JoinHandle<?>,
}
impl A {
pub fn run(&mut self) -> Result<()>{
self.thread = thread::spawn(move || {
let mut i = 0;
loop {
self.call();
i = 1 + i;
if i > 5 {
return
}
}
});
Ok(())
}
pub fn stop(&mut self) -> std::thread::Result<_> {
self.thread.join()
}
pub fn call(&mut self) {
println!("hello world");
}
}
fn main() {
let mut a = A{};
a.run();
}
我在thread: JoinHandle<?>
。 在這種情況下,線程的類型是什么。 我的代碼是否正確啟動和停止工作線程?
簡而言之, JoinHandle<T>
T
的join()
中的 T 返回傳遞給thread::spawn()
的閉包結果。 因此,在您的情況下, JoinHandle<?>
需要是JoinHandle<()>
因為您的閉包沒有返回任何內容,即()
(unit) 。
除此之外,您的虛擬代碼還包含一些其他問題。
run()
的返回類型不正確,至少需要為Result<(), ()>
。thread
字段需要是Option<JoinHandle<()>
才能處理fn stop(&mut self)
因為join()
消耗JoinHandle
。&mut self
傳遞給閉包,這會帶來更多問題,歸結為多個可變引用
Mutex<A>
來解決。 但是,如果您調用stop()
則可能會導致死鎖。但是,由於它是虛擬代碼,並且您在評論中進行了澄清。 讓我試着用幾個例子來澄清你的意思。 這包括我重寫你的虛擬代碼。
如果您在工作線程運行時不需要訪問數據,那么您可以創建一個新的struct WorkerData
。 然后在run()
中,您從A
復制/克隆所需的數據(或者我已將其重命名為Worker
)。 然后在閉包中你最終再次返回data
,所以你可以通過join()
獲取它。
use std::thread::{self, JoinHandle};
struct WorkerData {
...
}
impl WorkerData {
pub fn call(&mut self) {
println!("hello world");
}
}
struct Worker {
thread: Option<JoinHandle<WorkerData>>,
}
impl Worker {
pub fn new() -> Self {
Self { thread: None }
}
pub fn run(&mut self) {
// Create `WorkerData` and copy/clone whatever is needed from `self`
let mut data = WorkerData {};
self.thread = Some(thread::spawn(move || {
let mut i = 0;
loop {
data.call();
i = 1 + i;
if i > 5 {
// Return `data` so we get in through `join()`
return data;
}
}
}));
}
pub fn stop(&mut self) -> Option<thread::Result<WorkerData>> {
if let Some(handle) = self.thread.take() {
Some(handle.join())
} else {
None
}
}
}
你真的不需要thread
是Option<JoinHandle<WorkerData>>
而是可以只使用JoinHandle<WorkerData>>
。 因為如果您想再次調用run()
,重新分配持有Worker
的變量會更容易。
因此,現在我們可以簡化Worker
,刪除Option
並更改stop
以改為使用thread
,同時創建new() -> Self
代替run(&mut self)
。
use std::thread::{self, JoinHandle};
struct Worker {
thread: JoinHandle<WorkerData>,
}
impl Worker {
pub fn new() -> Self {
// Create `WorkerData` and copy/clone whatever is needed from `self`
let mut data = WorkerData {};
let thread = thread::spawn(move || {
let mut i = 0;
loop {
data.call();
i = 1 + i;
if i > 5 {
return data;
}
}
});
Self { thread }
}
pub fn stop(self) -> thread::Result<WorkerData> {
self.thread.join()
}
}
WorkerData
如果要在多個線程之間保留對WorkerData
的引用,則需要使用Arc
。 由於您還希望能夠對其進行變異,因此您需要使用Mutex
。
如果您只在單個線程中進行變異,那么您也可以選擇RwLock
,與Mutex
相比,它允許您同時鎖定和獲取多個不可變引用。
use std::sync::{Arc, RwLock};
use std::thread::{self, JoinHandle};
struct Worker {
thread: JoinHandle<()>,
data: Arc<RwLock<WorkerData>>,
}
impl Worker {
pub fn new() -> Self {
// Create `WorkerData` and copy/clone whatever is needed from `self`
let data = Arc::new(RwLock::new(WorkerData {}));
let thread = thread::spawn({
let data = data.clone();
move || {
let mut i = 0;
loop {
if let Ok(mut data) = data.write() {
data.call();
}
i = 1 + i;
if i > 5 {
return;
}
}
}
});
Self { thread, data }
}
pub fn stop(self) -> thread::Result<Arc<RwLock<WorkerData>>> {
self.thread.join()?;
// You might be able to unwrap and get the inner `WorkerData` here
Ok(self.data)
}
}
如果添加一個方法可以獲取Arc<RwLock<WorkerData>>
形式的data
。 然后,如果您在調用stop()
) 之前克隆Arc
並鎖定它(內部RwLock
),那么這將導致死鎖。 為避免這種情況,任何data()
方法都應返回&WorkerData
或&mut WorkerData
而不是Arc
。 這樣你就不能調用stop()
並導致死鎖。
如果你真的想停止工作線程,那么你必須使用一個標志來通知它這樣做。 您可以以共享AtomicBool
的形式創建標志。
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use std::thread::{self, JoinHandle};
struct Worker {
thread: JoinHandle<()>,
data: Arc<RwLock<WorkerData>>,
stop_flag: Arc<AtomicBool>,
}
impl Worker {
pub fn new() -> Self {
// Create `WorkerData` and copy/clone whatever is needed from `self`
let data = Arc::new(RwLock::new(WorkerData {}));
let stop_flag = Arc::new(AtomicBool::new(false));
let thread = thread::spawn({
let data = data.clone();
let stop_flag = stop_flag.clone();
move || {
// let mut i = 0;
loop {
if stop_flag.load(Ordering::Relaxed) {
break;
}
if let Ok(mut data) = data.write() {
data.call();
}
// i = 1 + i;
// if i > 5 {
// return;
// }
}
}
});
Self {
thread,
data,
stop_flag,
}
}
pub fn stop(self) -> thread::Result<Arc<RwLock<WorkerData>>> {
self.stop_flag.store(true, Ordering::Relaxed);
self.thread.join()?;
// You might be able to unwrap and get the inner `WorkerData` here
Ok(self.data)
}
}
如果您想要處理多種任務,分布在多個線程中,那么這里有一個更通用的示例。
您已經提到使用mpsc
。 因此,您可以使用Sender
和Receiver
以及自定義Task
和TaskResult
枚舉。
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
pub enum Task {
...
}
pub enum TaskResult {
...
}
pub type TaskSender = Sender<Task>;
pub type TaskReceiver = Receiver<Task>;
pub type ResultSender = Sender<TaskResult>;
pub type ResultReceiver = Receiver<TaskResult>;
struct Worker {
threads: Vec<JoinHandle<()>>,
task_sender: TaskSender,
result_receiver: ResultReceiver,
stop_flag: Arc<AtomicBool>,
}
impl Worker {
pub fn new(num_threads: usize) -> Self {
let (task_sender, task_receiver) = mpsc::channel();
let (result_sender, result_receiver) = mpsc::channel();
let task_receiver = Arc::new(Mutex::new(task_receiver));
let stop_flag = Arc::new(AtomicBool::new(false));
Self {
threads: (0..num_threads)
.map(|_| {
let task_receiver = task_receiver.clone();
let result_sender = result_sender.clone();
let stop_flag = stop_flag.clone();
thread::spawn(move || loop {
if stop_flag.load(Ordering::Relaxed) {
break;
}
let task_receiver = task_receiver.lock().unwrap();
if let Ok(task) = task_receiver.recv() {
drop(task_receiver);
// Perform the `task` here
// If the `Task` results in a `TaskResult` then create it and send it back
let result: TaskResult = ...;
// The `SendError` can be ignored as it only occurs if the receiver
// has already been deallocated
let _ = result_sender.send(result);
} else {
break;
}
})
})
.collect(),
task_sender,
result_receiver,
stop_flag,
}
}
pub fn stop(self) -> Vec<thread::Result<()>> {
drop(self.task_sender);
self.stop_flag.store(true, Ordering::Relaxed);
self.threads
.into_iter()
.map(|t| t.join())
.collect::<Vec<_>>()
}
#[inline]
pub fn request(&mut self, task: Task) {
self.task_sender.send(task).unwrap();
}
#[inline]
pub fn result_receiver(&mut self) -> &ResultReceiver {
&self.result_receiver
}
}
使用Worker
以及發送任務和接收任務結果的示例如下所示:
fn main() {
let mut worker = Worker::new(4);
// Request that a `Task` is performed
worker.request(task);
// Receive a `TaskResult` if any are pending
if let Ok(result) = worker.result_receiver().try_recv() {
// Process the `TaskResult`
}
}
在少數情況下,您可能需要為Task
和/或TaskResult
實現Send
。 查看“了解發送特征” 。
unsafe impl Send for Task {}
unsafe impl Send for TaskResult {}
JoinHandle 的類型參數應該是線程的JoinHandle
的返回類型。
在這種情況下,返回類型是一個空元組()
,發音為unit 。 當只有一個可能的值時使用它,並且在沒有指定返回類型時是函數的隱式“返回類型”。
你可以只寫JoinHandle<()>
來表示 function 不會返回任何東西。
(注意:您的代碼將遇到self.call()
的一些借用檢查器問題,這可能需要使用Arc<Mutex<Self>>
來解決,但這是另一個問題。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.