Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to terminate or suspend a Rust thread from another thread?

Tags:

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.

like image 842
fmvin Avatar asked Oct 05 '14 05:10

fmvin


People also ask

How a thread can be terminated?

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.

How many threads can Rust handle?

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.

What is thread in Rust?

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.


2 Answers

For both terminating and suspending a thread you can use channels.

Terminated externally

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(()); } 

Suspending and resuming

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(());     } } 

Other tools

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.

like image 182
Vladimir Matveev Avatar answered Sep 22 '22 15:09

Vladimir Matveev


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     } } 
like image 26
Rajeev Ranjan Avatar answered Sep 20 '22 15:09

Rajeev Ranjan