I am new to rust and come from a Java
background. Currently I'm trying to imitate the java methodology of thread creation using Runnable
interface in Rust. But I feel this design pattern isn't that flexible due to immutable self reference
that the run
method of StartThread
trait requires. This is my current implementation and would like to know better design methodology
thread.rs
use std::sync::{Arc, Mutex};
use std::thread;
pub struct RunningThreadInterface<T> {
pub instance: Arc<T>,
pub thread_join_handle: thread::JoinHandle<()>,
}
pub trait StartThread<T> {
fn start(self, thread_ID: String) -> RunningThreadInterface<T>;
fn run(&self);
}
pub trait TerminateThread {
fn stop(&mut self);
fn wait(self);
}
NetowrkReceiver.rs
use std::{thread, time};
use std::sync::{atomic::{AtomicBool, Ordering}, Arc, Mutex};
use crate::thread::*;
#[derive(Default)]
pub struct NetworkReceiverThread { // gets even complicated if it requires a lifetime parameter such as vector of dyn trait references
thread_ID: String,
// subscribers: Mutex<Vec<&'a dyn DataSubscriber>>,
terminate_flag: AtomicBool,
}
impl NetworkReceiverThread {
pub fn new() -> NetworkReceiverThread {
NetworkReceiverThread {
thread_ID: String::from(""),
terminate_flag: AtomicBool::new(false),
}
}
}
impl StartThread<NetworkReceiverThread> for NetworkReceiverThread {
fn start(mut self, thread_ID: String) -> RunningThreadInterface<NetworkReceiverThread> {
self.thread_ID = thread_ID.clone();
let network_receiver = Arc::new(self);
RunningThreadInterface {
instance: Arc::clone(&network_receiver),
thread_join_handle: thread::Builder::new().name(thread_ID).spawn(move || network_receiver.run()).ok().unwrap(),
}
}
fn run(&self) {
// let mut buff: [u8; 2048] = [0; 2048];
while !self.terminate_flag.load(Ordering::SeqCst) {
// receive network data and put into queue(will be processed by processor thread)
println!("receiver thread");
std::thread::sleep(time::Duration::from_secs(1));
}
}
}
impl TerminateThread for RunningThreadInterface<NetworkReceiverThread> {
fn stop(&mut self) {
self.instance.terminate_flag.store(true, Ordering::SeqCst);
}
fn wait(self) {
self.thread_join_handle.join();
}
}
main.rs
mod thread;
mod NetworkReceiver;
use std::time;
use thread::*;
use NetworkReceiver::NetworkReceiverThread;
fn main() {
let network_receiver = NetworkReceiverThread::new();
let mut network_receiver: RunningThreadInterface<NetworkReceiverThread> = network_receiver.start(String::from("NT"));
std::thread::sleep(time::Duration::from_secs(5));
network_receiver.stop();
network_receiver.wait();
}
1 Answer 1
There are two major challenges with what you are trying to do.
Firstly, how do you tell a thread to terminate? You can do what you've done, setting an atomic variable, but this only works for the very narrow case that you've got a loop that can repeatedly check a variable. If more plausibly, your thread is off doing heavy computation or making blocking calls it won't work.
Secondly, you want it to work regardless of the lifetime of your thread object. This is problematic because the spawn method requires that the spawned closure be 'static
. This means that the spawned object cannot contain any references with lifetimes shorter than "forever".
But let's take a step back and ask: why do you want to be able to terminate a thread? Generally speaking, threads work best if they manage their own lifetime. For example, a thread handling a single TCP connection can simply gracefully terminate when that connection is closed. If you don't want a particular thread to keep the program alive, just set it as a daemon thread.
struct
andtrait
. For Eg. I should be able to start a thread object by calling a kind ofstart
method of that object and also should be able to request thread termination likestop
andwait
for that thread tojoin
. Also the design should work even for thread object that takes lifetime parameter, with my current design this doesn't work. Basically a common way of creating thread objects and terminating them \$\endgroup\$