In this lesson, you will learn how to transition between states in a finite state machine (FSM) while ensuring data integrity across multiple concurrent threads.
The Challenge of Shared State
When designing an FSM, the "current state" is usually the heart of the system. In a single-threaded environment, a simple enum and a mutable variable suffice. However, in a concurrent system, multiple threads may attempt to trigger transitions simultaneously. If two threads read the current state as Idle and both try to move it to Processing, you risk inconsistent behavior or race conditions.
To keep transitions valid under contention, we must ensure that the "check" (is the transition valid?) and the "set" (update the state) happen as a single, atomic operation.
Atomic State Transitions
In Rust, the most efficient way to manage this is by using AtomicU8 (or AtomicUsize) to represent the state enum, combined with a compare_exchange loop. This allows us to ensure that a transition only occurs if the state is exactly what we expect it to be.
use std::sync::atomic::{AtomicU8, Ordering};
const IDLE: u8 = 0;
const PROCESSING: u8 = 1;
struct StateMachine {
state: AtomicU8,
}
impl StateMachine {
fn try_start_processing(&self) -> bool {
// Only transition if we are currently IDLE
self.state
.compare_exchange(IDLE, PROCESSING, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
}
By using compare_exchange, we eliminate the need for a global Mutex around the entire machine. If the compare_exchange fails, it means another thread successfully updated the state before we did; we can then decide whether to retry the operation or return an error to the caller, preventing invalid state jumps.
Designing for Atomicity
When building your FSM, map your states to numeric values. Ensure that every transition logic follows this "Read-Validate-Write" pattern in one atomic pulse. This keeps the machine responsive, avoids the latency of locking, and prevents the machine from entering an undefined state due to interleaved memory updates.
Key Takeaway: Use atomic compare_exchange operations to ensure that state transitions remain valid and thread-safe without the performance overhead of full-blown mutex locking.