簡體   English   中英

如何啟動和停止工作線程

[英]How to start and stop a worker thread

我有以下要求,這是其他編程語言的標准要求,但我不知道如何在 Rust 中執行。

我有一個 class,我想編寫一個方法來生成一個滿足 2 個條件的工作線程:

  • 產生工作線程后,返回 function (所以其他地方不需要等待)
  • 有一種機制可以停止這個線程。

例如,這是我的虛擬代碼:

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> Tjoin()中的 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
        }
    }
}

你真的不需要threadOption<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 因此,您可以使用SenderReceiver以及自定義TaskTaskResult枚舉。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM