Concurrency Fundamentals

Processes vs. Threads

A process is an independent executing instance of a program managed by the operating system with its own isolated virtual address space, file descriptor table, and process ID (PID). A thread is an independent unit of CPU execution scheduled within a process; all threads in the same process share the same virtual address space, heap memory, global static variables, and open file descriptors while maintaining their own private call stack and CPU register state.

Memory and Resource Isolation Architecture

+---------------------------------- Process A (PID 4102) ----------------------------------+
|  Shared Across All Threads in Process A:                                                 |
|    - Virtual Address Space & Code Segment (.text)                                        |
|    - Process Heap (`Box<T>`, `Vec<T>`, `Arc<T>`) & Global Statics (`OnceLock`, `Atomic*`)|
|    - OS File Descriptor Table (`std::fs::File`, `TcpStream`)                             |
|                                                                                          |
|  +---------------------------+  +---------------------------+                            |
|  | Thread 1 (main)           |  | Thread 2 (worker)         |                            |
|  | - Private Stack (8 MiB)   |  | - Private Stack (2 MiB)   |                            |
|  | - Private CPU Registers   |  | - Private CPU Registers   |                            |
|  | - Thread-Local Storage    |  | - Thread-Local Storage    |                            |
|  +---------------------------+  +---------------------------+                            |
+------------------------------------------------------------------------------------------+

+---------------------------------- Process B (PID 4103) ----------------------------------+
|  Completely Separate Virtual Address Space (Cannot read/write Process A's memory!)       |
+------------------------------------------------------------------------------------------+

Comparing Operating System Processes and Threads

Characteristic Process (std::process::Command) OS Thread (std::thread::spawn)
Virtual address space Isolated per process (hardware MMU page tables) Shared across all threads in the process
Creation overhead Higher (fork/exec or CreateProcess, new page tables) Lower (pthread_create / clone, allocates stack only)
Context-switch cost Higher (flushes TLB / switches page table base register) Lower (preserves virtual memory mappings)
Communication mechanism Inter-Process Communication (IPC: pipes, sockets, shared memory) Direct shared memory (Arc<T>, Mutex<T>, channels)
Fault isolation Crash or abort in child process does not kill parent process Uncaught abort (SIGSEGV / abort()) terminates the entire process; panic unwinds only the panicking thread
Default stack size in Rust OS main-thread default (typically 8 MiB) 2 MiB (configurable via std::thread::Builder::stack_size)

Inspecting Process ID and Thread IDs in Rust

Every Rust program starts with a single main thread inside a single OS process. Spawning additional threads via std::thread keeps the same std::process::id() while assigning each thread a unique std::thread::ThreadId:

use std::process;
use std::thread;

fn main() {
    let main_pid = process::id();
    let main_tid = thread::current().id();
    println!("main   -> PID: {}, ThreadId: {:?}", main_pid, main_tid);

    let worker = thread::spawn(|| {
        println!(
            "worker -> PID: {}, ThreadId: {:?}",
            process::id(),
            thread::current().id()
        );
    });

    worker.join().unwrap();
}
main   -> PID: 58214, ThreadId: ThreadId(1)
worker -> PID: 58214, ThreadId: ThreadId(2)

Choosing Between Multi-Processing and Multi-Threading

  • Use threads (std::thread or async worker pools) when tasks need high-bandwidth, low-latency access to shared in-memory data structures (Arc<T>) with minimal creation and context-switch overhead.
  • Use separate processes (std::process::Command) when running untrusted third-party binaries, isolating crash-prone native plugins, or enforcing OS-level privilege and memory boundaries.