Shared State

Back

Loading concept...

🏦 The Bank Vault Story: Rust Concurrency & Shared State

Imagine you and your friends all work at a magical bank. There’s ONE treasure vault everyone needs to access. How do you make sure nobody steps on each other’s toes?


🔐 The Big Picture: Sharing Data Between Threads

When multiple threads (workers) need to access the same data, we need rules. Otherwise, chaos! Rust gives us special “keys” and “locks” to keep everything safe.

Our Analogy: A bank vault with different types of locks and rules.

graph TD A["Shared Data"] --> B["Mutex - One at a time"] A --> C["RwLock - Many readers OR one writer"] B --> D["Arc - Share the key across threads"] B --> E["Poisoning - Lock broken if panic"] C --> F["Condvar - Wait for your turn"]

🔒 Mutex: The Single-Key Lock

What is it?

Mutex stands for “Mutual Exclusion”. Only ONE person can enter the vault at a time.

Simple Example:

  • You have a cookie jar in the kitchen
  • Only one kid can reach in at a time
  • Everyone else waits their turn

How It Works in Rust

use std::sync::Mutex;

fn main() {
    // Create a vault with 100 coins
    let vault = Mutex::new(100);

    // Lock it to get access
    let mut coins = vault.lock().unwrap();

    // Now we can change the value
    *coins += 50;

    println!("Coins: {}", coins);
    // Lock releases when `coins` goes away
}

🎯 Key Points

Concept Meaning
Mutex::new(value) Create a locked box
.lock() Ask for the key
.unwrap() Handle the Result
Auto-release Key returns when done

Remember: The lock is automatically released when the guard (coins) goes out of scope. No need to manually unlock!


🌐 Arc: The Copyable Key Holder

The Problem

A Mutex alone can’t be shared across threads. You need a way to give everyone a copy of the “key holder”.

What is Arc?

Arc = Atomically Reference Counted

Think of it like a magical key that can be cloned. Everyone gets their own copy, but they all open the same vault!

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    // Arc wraps the Mutex
    let vault = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..5 {
        // Clone the Arc (not the data!)
        let vault_clone = Arc::clone(&vault);

        let handle = thread::spawn(move || {
            let mut num = vault_clone.lock().unwrap();
            *num += 1;
        });

        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *vault.lock().unwrap());
}

🧠 Understanding the Flow

graph TD A["Original Arc"] -->|clone| B["Thread 1's Arc] A -->|clone| C[Thread 2's Arc"] A -->|clone| D[Thread 3's Arc] B --> E["Same Mutex"] C --> E D --> E E --> F["Shared Data"]

Why Arc, not Rc?

  • Rc = Single-threaded reference counting
  • Arc = Multi-threaded safe (uses atomic operations)

☠️ Mutex Poisoning: When Things Go Wrong

What Happens if a Thread Panics?

Imagine a worker enters the vault, then panics (crashes) while holding the key. The vault might be in a broken state!

Rust marks the Mutex as poisoned to warn everyone.

Example of Poisoning

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let vault = Arc::new(Mutex::new(0));
    let vault_clone = Arc::clone(&vault);

    // This thread will panic!
    let handle = thread::spawn(move || {
        let mut num = vault_clone.lock().unwrap();
        *num = 10;
        panic!("Oops! Something went wrong!");
    });

    // Wait for panic to happen
    let _ = handle.join();

    // Try to access the poisoned lock
    match vault.lock() {
        Ok(guard) => println!("Got: {}", *guard),
        Err(poisoned) => {
            println!("Lock was poisoned!");
            // We can still recover the data
            let guard = poisoned.into_inner();
            println!("Recovered: {}", *guard);
        }
    }
}

🚨 Key Takeaways

Situation What Happens
Normal unlock Next thread gets the lock
Panic while holding Lock becomes poisoned
Try to lock poisoned Returns Err(PoisonError)
Recovery possible? Yes! Use .into_inner()

📚 RwLock: Many Readers, One Writer

The Problem with Mutex

What if most workers just want to read the vault contents? With Mutex, they still wait in line one by one. Wasteful!

RwLock to the Rescue

RwLock = Read-Write Lock

  • Many can read at the same time
  • Only ONE can write (and no readers during write)
use std::sync::RwLock;

fn main() {
    let vault = RwLock::new(5);

    // Multiple readers - OK!
    {
        let r1 = vault.read().unwrap();
        let r2 = vault.read().unwrap();
        println!("Readers see: {} and {}", *r1, *r2);
    }

    // One writer at a time
    {
        let mut w = vault.write().unwrap();
        *w += 10;
        println!("Writer changed to: {}", *w);
    }
}

📊 Comparison Table

Feature Mutex RwLock
Readers at once 1 Many
Writers at once 1 1
Read + Write together No No
Best for Mostly writing Mostly reading

When to Use What?

graph TD A["Need shared state?"] -->|Yes| B{Mostly reads?} B -->|Yes| C["Use RwLock"] B -->|No| D["Use Mutex"] A -->|No| E["Use regular data"]

⏳ Condvar: Wait for the Right Moment

What is Condvar?

Condvar = Condition Variable

It’s like a waiting room. Threads can sleep until someone wakes them up!

Real-life example:

  • You’re at a restaurant
  • The food isn’t ready yet
  • You sit and wait (sleep)
  • The chef rings a bell when ready (notify)

How It Works

use std::sync::{Arc, Mutex, Condvar};
use std::thread;

fn main() {
    let pair = Arc::new((Mutex::new(false), Condvar::new()));
    let pair_clone = Arc::clone(&pair);

    // Waiter thread
    thread::spawn(move || {
        let (lock, cvar) = &*pair_clone;
        let mut ready = lock.lock().unwrap();

        while !*ready {
            // Sleep until notified
            ready = cvar.wait(ready).unwrap();
        }

        println!("Food is ready! Eating now!");
    });

    // Chef thread (main)
    thread::sleep(std::time::Duration::from_secs(1));

    let (lock, cvar) = &*pair;
    let mut ready = lock.lock().unwrap();
    *ready = true;

    // Wake up the waiter!
    cvar.notify_one();

    println!("Chef: Order up!");
}

🔔 Condvar Methods

Method What it does
wait(guard) Sleep until notified
notify_one() Wake ONE waiting thread
notify_all() Wake ALL waiting threads
wait_timeout() Wait with a time limit

Why Use Condvar?

Without Condvar: Thread keeps checking in a loop (wastes CPU)

With Condvar: Thread sleeps efficiently, wakes only when needed


🎯 Putting It All Together

Here’s a complete example using everything we learned:

use std::sync::{Arc, Mutex, Condvar};
use std::thread;

fn main() {
    // Shared counter with condition variable
    let data = Arc::new((
        Mutex::new(0),
        Condvar::new()
    ));

    let data_clone = Arc::clone(&data);

    // Producer thread
    let producer = thread::spawn(move || {
        for i in 1..=5 {
            let (lock, cvar) = &*data_clone;
            let mut count = lock.lock().unwrap();
            *count = i;
            println!("Produced: {}", i);
            cvar.notify_one();
            drop(count); // Release lock
            thread::sleep(
                std::time::Duration::from_millis(100)
            );
        }
    });

    // Consumer waits and reacts
    let (lock, cvar) = &*data;
    for _ in 1..=5 {
        let mut count = lock.lock().unwrap();
        while *count == 0 {
            count = cvar.wait(count).unwrap();
        }
        println!("Consumed: {}", *count);
        *count = 0;
    }

    producer.join().unwrap();
}

🧠 Quick Summary

Tool Purpose Analogy
Mutex One thread at a time Single key to vault
Arc Share across threads Cloneable key holder
Poisoning Detect panicked locks Broken vault warning
RwLock Many readers OR one writer Library book rules
Condvar Wait for conditions Restaurant waiting room

💡 Golden Rules

  1. Always use Arc with Mutex when sharing across threads
  2. Handle poison errors gracefully - data might still be valid
  3. Use RwLock when reads vastly outnumber writes
  4. Use Condvar instead of busy-waiting loops
  5. Keep lock time short - grab, do work, release!

You’ve now unlocked the secrets of Rust’s concurrency primitives! These tools let you safely share data between threads without fear. Go build something amazing! 🚀

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.