Introduction
In Rust, AtomicU32
is an atomic integer type that provides thread-safe operations on 32-bit unsigned integers. It is part of the standard library's std::sync::atomic
module and is useful for shared state between threads where you need to perform atomic operations without using locks.
Using AtomicU32
To use AtomicU32
, you need to import it from the std::sync::atomic
module. You can create an AtomicU32
by using the AtomicU32::new
method. Atomic operations such as store
, load
, fetch_add
, and compare_and_swap
can be used to manipulate the value safely across threads.
use std::sync::atomic::{AtomicU32, Ordering};
fn main() {
let atomic = AtomicU32::new(5);
println!("Initial value: {}", atomic.load(Ordering::Relaxed));
atomic.store(10, Ordering::Relaxed);
println!("Updated value: {}", atomic.load(Ordering::Relaxed));
}
// Output:
// Initial value: 5
// Updated value: 10
Key Methods
Below are some of the key methods exposed by the AtomicU32
class:
AtomicU32::new
Creates a new AtomicU32
with the given initial value.
use std::sync::atomic::AtomicU32;
fn main() {
let atomic = AtomicU32::new(10);
println!("Created AtomicU32 with initial value: {}", atomic.load(Ordering::Relaxed));
}
// Output:
// Created AtomicU32 with initial value: 10
load
Returns the current value of the AtomicU32
.
use std::sync::atomic::{AtomicU32, Ordering};
fn main() {
let atomic = AtomicU32::new(20);
println!("Current value: {}", atomic.load(Ordering::Relaxed));
}
// Output:
// Current value: 20
store
Sets the value of the AtomicU32
.
use std::sync::atomic::{AtomicU32, Ordering};
fn main() {
let atomic = AtomicU32::new(30);
atomic.store(40, Ordering::Relaxed);
println!("Updated value: {}", atomic.load(Ordering::Relaxed));
}
// Output:
// Updated value: 40
compare_and_swap
Compares the current value with current
, and if they are equal, sets it to new
.
use std::sync::atomic::{AtomicU32, Ordering};
fn main() {
let atomic = AtomicU32::new(50);
let old_value = atomic.compare_and_swap(50, 60, Ordering::Relaxed);
println!("Old value: {}, New value: {}", old_value, atomic.load(Ordering::Relaxed));
}
// Output:
// Old value: 50
// New value: 60
fetch_add
Adds to the current value, returning the previous value.
use std::sync::atomic::{AtomicU32, Ordering};
fn main() {
let atomic = AtomicU32::new(70);
let previous = atomic.fetch_add(10, Ordering::Relaxed);
println!("Previous value: {}, New value: {}", previous, atomic.load(Ordering::Relaxed));
}
// Output:
// Previous value: 70
// New value: 80
fetch_sub
Subtracts from the current value, returning the previous value.
use std::sync::atomic::{AtomicU32, Ordering};
fn main() {
let atomic = AtomicU32::new(100);
let previous = atomic.fetch_sub(20, Ordering::Relaxed);
println!("Previous value: {}, New value: {}", previous, atomic.load(Ordering::Relaxed));
}
// Output:
// Previous value: 100
// New value: 80
Example Usage
Example 1: Basic Usage
use std::sync::atomic::{AtomicU32, Ordering};
fn main() {
let atomic = AtomicU32::new(5);
println!("Initial value: {}", atomic.load(Ordering::Relaxed));
atomic.store(10, Ordering::Relaxed);
println!("Updated value: {}", atomic.load(Ordering::Relaxed));
}
// Output:
// Initial value: 5
// Updated value: 10
Example 2: Using compare_and_swap
use std::sync::atomic::{AtomicU32, Ordering};
fn main() {
let atomic = AtomicU32::new(50);
let old_value = atomic.compare_and_swap(50, 60, Ordering::Relaxed);
println!("Old value: {}, New value: {}", old_value, atomic.load(Ordering::Relaxed));
}
// Output:
// Old value: 50
// New value: 60
Example 3: Using fetch_add
use std::sync::atomic::{AtomicU32, Ordering};
fn main() {
let atomic = AtomicU32::new(70);
let previous = atomic.fetch_add(10, Ordering::Relaxed);
println!("Previous value: {}, New value: {}", previous, atomic.load(Ordering::Relaxed));
}
// Output:
// Previous value: 70
// New value: 80
Example 4: Using fetch_sub
use std::sync::atomic::{AtomicU32, Ordering};
fn main() {
let atomic = AtomicU32::new(100);
let previous = atomic.fetch_sub(20, Ordering::Relaxed);
println!("Previous value: {}, New value: {}", previous, atomic.load(Ordering::Relaxed));
}
// Output:
// Previous value: 100
// New value: 80
Example 5: Using with Multiple Threads
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::thread;
fn main() {
let atomic = Arc::new(AtomicU32::new(0));
let atomic1 = Arc::clone(&atomic);
let
<pre><code class="rust">
handle1 = thread::spawn(move || {
atomic1.fetch_add(1, Ordering::Relaxed);
});
let atomic2 = Arc::clone(&atomic);
let handle2 = thread::spawn(move || {
atomic2.fetch_add(1, Ordering::Relaxed);
});
handle1.join().unwrap();
handle2.join().unwrap();
println!("Final value: {}", atomic.load(Ordering::Relaxed));
}
// Output:
// Final value: 2
Considerations
- Operations on
AtomicU32
are lock-free and thread-safe. - Always use the appropriate
Ordering
when performing atomic operations to ensure the correct memory ordering guarantees. - Use
AtomicU32
when you need to share a simple integer state across threads without the overhead of using a mutex.
See Also
- AtomicUsize - An atomic integer type for shared state between threads.
- Mutex - A mutual exclusion primitive for protecting shared data.
- RwLock - A read-write lock allowing multiple readers or a single writer.