Course contentsShow
← Rust
Lesson 1096 of 1,91010. Concurrency PrimitivesFree lesson

Graceful Thread Termination Protocols

You will implement signaling mechanisms to notify background threads to shut down during cleanup.

In this lesson, you will learn how to implement a signaling mechanism to gracefully notify background threads that it is time to shut down.

In Rust, simply dropping a thread's JoinHandle doesn't stop the code inside the thread. If your background thread is stuck in an infinite loop {}, it will continue to run until the process terminates, which can lead to resource leaks or incomplete data processing. To terminate gracefully, you must create a communication channel that the thread periodically checks to see if it should exit.

The Signaling Pattern

The most robust way to achieve this is by using an AtomicBool wrapped in an Arc. The Arc allows multiple threads to hold a reference to the same memory location, while the AtomicBool provides a thread-safe, lock-free way to flip a "stop" switch.

Here is how you implement this protocol:

use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use std::thread;
use std::time::Duration;

fn main() {
    let running = Arc::new(AtomicBool::new(true));
    let r = running.clone();

    let handle = thread::spawn(move || {
        // The worker thread checks the atomic flag in every iteration
        while r.load(Ordering::SeqCst) {
            println!("Working...");
            thread::sleep(Duration::from_millis(100));
        }
        println!("Cleanup complete, thread exiting.");
    });

    // Simulate main thread doing work, then requesting shutdown
    thread::sleep(Duration::from_millis(500));
    running.store(false, Ordering::SeqCst);

    handle.join().unwrap();
}

Why this works

By checking the AtomicBool at the start of each loop iteration, the thread remains responsive. We use Ordering::SeqCst here to ensure that when we update the flag in the main thread, the background thread sees the change immediately across all CPU cores. Once the thread observes the false value, it breaks out of its loop, performs any necessary cleanup, and finishes its execution, allowing the main thread to join() it safely. This pattern is far superior to "killing" a thread, as it gives the background process a chance to close file handles or flush buffers to disk properly.

Key Takeaway: Always use shared atomic state to signal termination, ensuring background threads have a clear, safe, and controlled path to exit their execution loops.