Rust Concurrency: Data Sharing Between Threads

Sharred Memory Between Threads in Rust

While Go promotes communication via channels, Rust provides shared memory concurrency as a core primitive. This approach, though potentially error-prone, offers performance benefits and is fundamental to systems programming.

Core Concepts

Rust's shared memory concurrency relies on two key components:

  • Arc<T> (Atomic Reference Counting): Allows multiple threads to share ownership of data. Unlike Rc, Arc uses atomic operations for thread-safe reference counting.
  • Mutex<T> (Mutual Exclusion): Ansures only one thread can access the shared data at a time, preventing data races.

Implementation Example

The following demonstrates a counter shared across mulitple threads:

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

fn main() {
    let shared_counter = Arc::new(Mutex::new(0));
    let mut thread_handles = Vec::new();
    
    for _ in 0..10 {
        let counter_ref = Arc::clone(&shared_counter);
        let handle = thread::spawn(move || {
            let mut guard = counter_ref.lock().unwrap();
            *guard += 1;
            println!("Thread {:?} incremented to {}", 
                    thread::current().id(), *guard);
        });
        thread_handles.push(handle);
    }
    
    for handle in thread_handles {
        handle.join().unwrap();
    }
    
    println!("Final count: {}", *shared_counter.lock().unwrap());
}

Deadlock Scenario

Deadlocks occur when threads wait for each other's resources. This example creates a deliberate deadlock:

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

fn main() {
    let resource_a = Arc::new(Mutex::new(5));
    let resource_b = Arc::new(Mutex::new(10));
    
    let a1 = Arc::clone(&resource_a);
    let b1 = Arc::clone(&resource_b);
    let thread_one = thread::spawn(move || {
        let _lock_a = a1.lock().unwrap();
        thread::sleep(Duration::from_millis(10));
        let _lock_b = b1.lock().unwrap(); // May never acquire
        println!("Thread one acquired both resources");
    });
    
    let a2 = Arc::clone(&resource_a);
    let b2 = Arc::clone(&resource_b);
    let thread_two = thread::spawn(move || {
        let _lock_b = b2.lock().unwrap();
        thread::sleep(Duration::from_millis(10));
        let _lock_a = a2.lock().unwrap(); // May never acquire
        println!("Thread two acquired both resources");
    });
    
    thread_one.join().unwrap();
    thread_two.join().unwrap();
}

Send and Sync Traits

Rust uses marker traits to enforce thread safety:

  • Send: Types implementing Send can be transferred between threads. Most Rust types are Send, except Rc<T>.
  • Sync: Types implementing Sync can be referenced from multiple threads safely. Types composed of Sync types are automatically Sync.

Arc's implementation ensures thread safety:

// Arc is both Send and Sync when the contained type is
unsafe impl<t: send="" sync=""> Send for Arc<t> {}
unsafe impl<t: send="" sync=""> Sync for Arc<t> {}</t></t:></t></t:>

Best Practices

  1. Use Arc for shared ownership across threads instead of Rc
  2. Protect shared data with Mutex to prevent concurrent modification
  3. Avoid deadlocks by:
    • Acquiring locks in consistent order
    • Using try_lock when appropriate
    • Implementing timeout mechanisms
  4. Leverage atomic types from std::sync::atomic for simple operations

Tags: rust Concurrency multithreading ARC mutex

Posted on Wed, 13 May 2026 11:47:39 +0000 by nextman