When a thread is executing, its context is typically stored in the stack memory. The key elements stored in the stack for thread context usually include:
But that begs this question :What if the context references to data defined in calling function?
There comes, Dangling pointer and Undefined behaviour. When the data being referenced literally hits the end of its lifetime, every other variables that hold the reference to it will point to null. And it boils down to the common synchronization problem.
So how can we circumvent the race condition or undefiend behaviour issues? One way to do it is to use thread-local storage.
Variables stored in thread-local storage mean the following:
The following example shows how you can declare thread-local resource and what happens when those values are accessed by multiple threads.
use std::cell::Cell;
use std::thread;
thread_local! {
static COUNTER: Cell<u32> = Cell::new(0);
}
fn increment_counter() {
COUNTER.with(|c| {
let value = c.get();
c.set(value + 1);
println!(
"Thread {:?}: counter value = {}",
thread::current().id(),
c.get()
);
});
}
#[test]
fn t() {
// Spawn multiple threads
let handles: Vec<_> = (0..3)
.map(|_| {
thread::spawn(|| {
for _ in 0..5 {
increment_counter();
}
})
})
.collect();
// Wait for all threads to complete
for handle in handles {
handle.join().unwrap();
}
// Check the counter in the main thread
COUNTER.with(|c| {
println!("Main thread: counter value = {}", c.get());
});
}
Thread ThreadId(3): counter value = 1
Thread ThreadId(3): counter value = 2
Thread ThreadId(3): counter value = 3
Thread ThreadId(3): counter value = 4
Thread ThreadId(3): counter value = 5
Thread ThreadId(4): counter value = 1
Thread ThreadId(4): counter value = 2
Thread ThreadId(4): counter value = 3
Thread ThreadId(4): counter value = 4
Thread ThreadId(4): counter value = 5
Thread ThreadId(5): counter value = 1
Thread ThreadId(5): counter value = 2
Thread ThreadId(5): counter value = 3
Thread ThreadId(5): counter value = 4
Thread ThreadId(5): counter value = 5