backend / async concurrency / 03_threads_vs_processes.md

Difference Between Threads and Processes

4 interview angles 4 min read source

Difference Between Threads and Processes

The difference between threads and processes in computing is related to how tasks are executed and managed within a program. Here’s a detailed explanation of both:

1. Processes:

A process is an independent program that runs in its own memory space. Each process has its own allocated resources, such as memory, file handles, and other system resources.

Key Characteristics of Processes:

  • Memory Isolation: Each process runs in its own memory space and does not share memory with other processes. This makes processes more isolated and less prone to affecting each other.
  • Resource Allocation: Processes are allocated their own resources (CPU, memory, I/O), which can make them more resource-intensive compared to threads.
  • Independent Execution: Processes are independent and do not share any state with each other. If one process crashes, it doesn’t affect other processes.
  • Communication: Processes need Inter-Process Communication (IPC) mechanisms such as pipes, shared memory, or message passing to communicate with each other.
  • Concurrency: Multiple processes can be run concurrently by the operating system, either on multiple cores or via time-sharing on a single core.

Example:

import os

def child_process():
    print(f"Child Process: {os.getpid()}")

def parent_process():
    print(f"Parent Process: {os.getpid()}")

if __name__ == "__main__":
    print("Main Process")
    pid = os.fork()  # Create a child process
    if pid == 0:
        child_process()  # Child process code
    else:
        parent_process()  # Parent process code

2. Threads:

A thread is a smaller unit of execution within a process. Threads share the same memory space and resources of the parent process but can run independently.

Key Characteristics of Threads:

  • Shared Memory: Threads within the same process share the same memory space, meaning they can read and write to the same variables. This makes threads lightweight compared to processes, but also introduces risks like race conditions.
  • Resource Sharing: Since threads share resources like memory, file handles, and other data, they are less resource-intensive than processes.
  • Communication: Threads can communicate easily with each other since they share the same memory space, but they also need synchronization mechanisms (e.g., locks) to prevent conflicts and race conditions.
  • Concurrency: Threads within the same process can run concurrently, but because they share memory, care must be taken to ensure that their operations do not interfere with each other.
  • Lightweight: Threads are generally more lightweight compared to processes, making them suitable for tasks that require high concurrency with minimal overhead.

Example:

import threading

def print_thread():
    print(f"Thread ID: {threading.get_ident()}")

if __name__ == "__main__":
    thread1 = threading.Thread(target=print_thread)
    thread2 = threading.Thread(target=print_thread)
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()

Key Differences Between Threads and Processes:

Aspect Processes Threads
Memory Each process has its own separate memory space. Threads share the same memory space within a process.
Resource Usage Processes are more resource-intensive (each has its own memory, CPU resources, etc.). Threads are lightweight and share resources.
Independence Processes are independent and don’t share any state. Threads share the same memory space, so they can affect each other.
Communication Communication between processes requires Inter-Process Communication (IPC). Threads can communicate easily by sharing memory.
Performance Processes are more expensive to create and manage. Threads are faster to create and require fewer resources.
Crash Behavior If a process crashes, it does not affect other processes. If a thread crashes, it can potentially bring down the entire process.
Use Cases Processes are useful for tasks that require full isolation or are resource-intensive. Threads are ideal for tasks that require concurrency and share resources.

Summary:

  • Processes are independent units of execution with their own memory and resources. They are more isolated but can be heavier on resources and slower to create.
  • Threads are lightweight units of execution within a process. They share the same memory and resources, which allows for easier communication but requires careful synchronization to avoid issues like race conditions.

import os

def child_process():
    print(f"Child Process: {os.getpid()}")

def parent_process():
    print(f"Parent Process: {os.getpid()}")

if __name__ == "__main__":
    print("Main Process")
    pid = os.fork()  # Create a child process
    if pid == 0:
        child_process()  # Child process code
    else:
        parent_process()  # Parent process code
import threading

def print_thread():
    print(f"Thread ID: {threading.get_ident()}")

if __name__ == "__main__":
    thread1 = threading.Thread(target=print_thread)
    thread2 = threading.Thread(target=print_thread)
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()

Interview angle

  • “Threads or processes?” - threads share an address space, so they’re cheap to create and communicate through shared memory, but a crash takes the whole process and the GIL limits CPU parallelism. Processes are isolated, give true parallelism, and pay for it in startup cost and IPC serialisation.
  • “What does a process actually cost?” - its own interpreter and memory, plus pickling for every argument and return value. Sending large objects to a process pool can cost more than the computation saves; measure before parallelising.
  • “When is the crash-isolation argument decisive?” - when running untrusted or segfault-prone native code. A process boundary contains the failure; a thread does not.
  • “How do they communicate?” - threads share objects directly and therefore need locks; processes need explicit channels (Queue, Pipe, shared memory), which is more code but removes an entire class of race condition.