Editor's note — this example was created before Rust 1.0 and the specific types have changed or been removed since then. The general question and concept remains valid.
I have spawned a thread with an infinite loop and timer inside.
thread::spawn(|| { let mut timer = Timer::new().unwrap(); let periodic = timer.periodic(Duration::milliseconds(200)); loop { periodic.recv(); // Do my work here } });
After a time based on some conditions, I need to terminate this thread from another part of my program. In other words, I want to exit from the infinite loop. How can I do this correctly? Additionally, how could I to suspend this thread and resume it later?
I tried to use a global unsafe flag to break the loop, but I think this solution does not look nice.
A thread automatically terminates when it returns from its entry-point routine. A thread can also explicitly terminate itself or terminate any other thread in the process, using a mechanism called cancelation.
The threads provided by the Rust standard library are "OS threads", that is, they use the facilities of your operating system. Therefore, a Rust program has no limit imposed by Rust itself, but rather, this limit would result from whatever your OS lets you do.
The threading modelAn executing Rust program consists of a collection of native OS threads, each with their own stack and local state. Threads can be named, and provide some built-in support for low-level synchronization.
For both terminating and suspending a thread you can use channels.
On each iteration of a worker loop, we check if someone notified us through a channel. If yes or if the other end of the channel has gone out of scope we break the loop.
use std::io::{self, BufRead}; use std::sync::mpsc::{self, TryRecvError}; use std::thread; use std::time::Duration; fn main() { println!("Press enter to terminate the child thread"); let (tx, rx) = mpsc::channel(); thread::spawn(move || loop { println!("Working..."); thread::sleep(Duration::from_millis(500)); match rx.try_recv() { Ok(_) | Err(TryRecvError::Disconnected) => { println!("Terminating."); break; } Err(TryRecvError::Empty) => {} } }); let mut line = String::new(); let stdin = io::stdin(); let _ = stdin.lock().read_line(&mut line); let _ = tx.send(()); }
We use recv()
which suspends the thread until something arrives on the channel. In order to resume the thread, you need to send something through the channel; the unit value ()
in this case. If the transmitting end of the channel is dropped, recv()
will return Err(())
- we use this to exit the loop.
use std::io::{self, BufRead}; use std::sync::mpsc; use std::thread; use std::time::Duration; fn main() { println!("Press enter to wake up the child thread"); let (tx, rx) = mpsc::channel(); thread::spawn(move || loop { println!("Suspending..."); match rx.recv() { Ok(_) => { println!("Working..."); thread::sleep(Duration::from_millis(500)); } Err(_) => { println!("Terminating."); break; } } }); let mut line = String::new(); let stdin = io::stdin(); for _ in 0..4 { let _ = stdin.lock().read_line(&mut line); let _ = tx.send(()); } }
Channels are the easiest and the most natural (IMO) way to do these tasks, but not the most efficient one. There are other concurrency primitives which you can find in the std::sync
module. They belong to a lower level than channels but can be more efficient in particular tasks.
The ideal solution would be a Condvar
. You can use wait_timeout
in the std::sync module
, as pointed out by @Vladimir Matveev.
This is the example from the documentation:
use std::sync::{Arc, Mutex, Condvar}; use std::thread; use std::time::Duration; let pair = Arc::new((Mutex::new(false), Condvar::new())); let pair2 = pair.clone(); thread::spawn(move|| { let &(ref lock, ref cvar) = &*pair2; let mut started = lock.lock().unwrap(); *started = true; // We notify the condvar that the value has changed. cvar.notify_one(); }); // wait for the thread to start up let &(ref lock, ref cvar) = &*pair; let mut started = lock.lock().unwrap(); // as long as the value inside the `Mutex` is false, we wait loop { let result = cvar.wait_timeout(started, Duration::from_millis(10)).unwrap(); // 10 milliseconds have passed, or maybe the value changed! started = result.0; if *started == true { // We received the notification and the value has been updated, we can leave. break } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With