Introduction
In Rust, AtomicUsize
is an atomic integer type that provides thread-safe operations on the platform's native pointer-sized 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 AtomicUsize
To use AtomicUsize
, you need to import it from the std::sync::atomic
module. You can create an AtomicUsize
by using the AtomicUsize::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::{AtomicUsize, Ordering};
fn main() {
let atomic = AtomicUsize::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 AtomicUSize
class:
AtomicUsize::new
Creates a new AtomicUsize
with the given initial value.
use std::sync::atomic::AtomicUsize;
fn main() {
let atomic = AtomicUsize::new(10);
println!("Created AtomicUsize with initial value: {}", atomic.load(Ordering::Relaxed));
}
// Output:
// Created AtomicUsize with initial value: 10
load
Returns the current value of the AtomicUsize
.
use std::sync::atomic::{AtomicUsize, Ordering};
fn main() {
let atomic = AtomicUsize::new(20);
println!("Current value: {}", atomic.load(Ordering::Relaxed));
}
// Output:
// Current value: 20
store
Sets the value of the AtomicUsize
.
use std::sync::atomic::{AtomicUsize, Ordering};
fn main() {
let atomic = AtomicUsize::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::{AtomicUsize, Ordering};
fn main() {
let atomic = AtomicUsize::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::{AtomicUsize, Ordering};
fn main() {
let atomic = AtomicUsize::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::{AtomicUsize, Ordering};
fn main() {
let atomic = AtomicUsize::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::{AtomicUsize, Ordering};
fn main() {
let atomic = AtomicUsize::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::{AtomicUsize, Ordering};
fn main() {
let atomic = AtomicUsize::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::{AtomicUsize, Ordering};
fn main() {
let atomic = AtomicUsize::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::{AtomicUsize, Ordering};
fn main() {
let atomic = AtomicUsize::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::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
fn main() {
let atomic = Arc::new(AtomicUsize::new(0));
let atomic1 = Arc::clone(&atomic);
let 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
AtomicUsize
are lock-free and thread-safe. - Always use the appropriate
Ordering
when performing atomic operations to ensure the correct memory ordering guarantees. - Use
AtomicUsize
when you need to share a simple integer state across threads without the overhead of using a mutex.