[英]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.