processes vs threads
Processes vs Threads
Learn how processes provide resource ownership and isolation, while threads provide independently schedulable execution paths that share the resources of their containing process.
Introduction
An operating system must support several applications and activities at the same time. Processes and threads are two fundamental abstractions used to organize this concurrent execution.
- A process is a running program environment with an independent virtual address space and operating-system-managed resources.
- A thread is an execution path within a process. Threads belonging to the same process share process resources while maintaining separate execution state.
Processes emphasize isolation and resource ownership. Threads emphasize concurrent execution and efficient sharing within one application.
Core distinction: A process is primarily a resource and protection boundary. A thread is primarily an execution and scheduling unit inside that boundary.
In your System Design curriculum, this is Topic 2.2 under Computer Systems, Linux and Concurrency. The module uses processes, threads, synchronization, race conditions and deadlocks to establish local concurrency foundations before introducing distributed coordination.
Prerequisites
| # | Prerequisite | Why It Is Needed |
|---|---|---|
| 1 | CPU, memory, disk and network costs | Processes and threads consume processor time, memory and operating-system resources. |
| 2 | Basic operating-system knowledge | The operating system creates, schedules and terminates execution units. |
| 3 | Functions and pointers | Thread entry functions and shared data commonly use function and pointer interfaces. |
| 4 | Memory concepts | The key difference involves isolated process memory and shared thread memory. |
| 5 | Concurrency basics | Concurrent execution introduces ordering, coordination and data-safety concerns. |
Program, Process and Thread
| Concept | Description |
|---|---|
| Program | A passive collection of executable instructions and associated data |
| Process | An active execution environment created for a running program |
| Thread | An independently schedulable instruction stream within a process |
A process does not execute instructions without a thread. A newly created process normally begins with an initial thread and can create additional threads when concurrent execution is required.
What Is a Process?
A process is an operating-system-managed environment in which a program executes. It gives the running application a protected address space and access to assigned resources.
A process commonly contains:
- Virtual address space
- Executable code
- Global and static data
- Heap memory
- One or more thread stacks
- Open files and sockets
- Security identity and permissions
- Environment and runtime state
- At least one thread of execution
Simplified Process Model
Process
+---------------------------------------------+
| Virtual Address Space |
| |
| Executable Code |
| Global and Static Data |
| Heap |
| |
| Thread 1 Stack Thread 2 Stack |
| Thread 1 State Thread 2 State |
| |
| Open Files, Sockets and Other Resources |
+---------------------------------------------+
Process Identity
Operating systems assign a process identifier, commonly called a PID, to distinguish one process from another.
A process can also have relationships such as:
- Parent process
- Child process
- Process group
- Session
- Security or user identity
The exact process model and available relationships depend on the operating system.
Process Isolation
Separate processes normally have separate virtual address spaces. Ordinary memory writes performed by one process do not directly modify another process's private memory.
Process A Process B
+--------------------+ +--------------------+
| Address Space A | | Address Space B |
| | | |
| Global Data A | | Global Data B |
| Heap A | | Heap B |
| Thread Stack A | | Thread Stack B |
+--------------------+ +--------------------+
Ordinary writable memory is isolated.
Isolation provides several benefits:
- A memory error is more likely to remain within one process boundary.
- Processes can run with different permissions.
- One process cannot normally read another process's private memory directly.
- Independent processes can be started, stopped or replaced separately.
- Process boundaries provide useful security and fault-isolation controls.
Isolation limit: Process isolation reduces the direct effect of many failures, but processes can still share files, databases, networks, kernels and other dependencies that create common failure paths.
Process States
A process or its threads can move through operating-system scheduling states. Exact names vary, but a simplified lifecycle includes:
Created
|
v
Ready
|
v
Running
|
+----------> Waiting / Blocked
| |
| v
+--------------- Ready
|
v
Terminated
| State | Meaning |
|---|---|
| Created | The operating system is preparing the execution environment |
| Ready | The execution unit can run when processor time is assigned |
| Running | Instructions are executing on a processor |
| Waiting or blocked | Execution is waiting for I/O, a lock, a timer or another event |
| Terminated | Execution has completed or been stopped |
What Is a Thread?
A thread is an independently schedulable sequence of instructions within a process.
Each thread normally has its own:
- Program counter
- CPU register state
- Stack
- Scheduling state
- Thread identifier
- Selected thread-local data
Threads in the same process commonly share:
- Executable code
- Global and static data
- Heap memory
- Process address space
- Open files and sockets
- Process-level configuration and identity
Multithreaded Process
One Process
+------------------------------------------------+
| Shared Code |
| Shared Global Data |
| Shared Heap |
| Shared Open Files and Sockets |
| |
| Thread A Thread B Thread C |
| +----------+ +----------+ +----------+|
| | Registers| | Registers| | Registers||
| | Counter | | Counter | | Counter ||
| | Stack A | | Stack B | | Stack C ||
| +----------+ +----------+ +----------+|
+------------------------------------------------+
Processes vs Threads
| Area | Process | Thread |
|---|---|---|
| Primary role | Resource ownership and protection boundary | Execution and scheduling unit |
| Address space | Normally isolated from other processes | Shared with peer threads in the same process |
| Heap and globals | Private to the process unless explicitly shared | Shared among threads in the process |
| Stack | Contains at least one thread stack | Each thread has its own stack |
| Communication | Requires IPC or explicitly shared resources | Can communicate through shared memory |
| Creation overhead | Generally requires a new process environment | Uses the existing process environment |
| Failure isolation | Normally stronger | A fatal fault can terminate the containing process |
| Coordination risk | IPC and distributed-state complexity | Shared-memory races and synchronization defects |
| Parallelism | Processes can execute on different CPU cores | Threads can execute on different CPU cores |
| Typical use | Isolation, security separation and independent services | Concurrent work within one application |
Inter-process Communication
Because processes normally have isolated address spaces, processes require an explicit communication mechanism.
Common IPC mechanisms include:
- Pipes
- Named pipes
- Sockets
- Shared memory
- Message queues
- Signals
- Files
- Operating-system-specific communication facilities
Pipe-based Flow
Process A
|
| Write bytes
v
Operating-system Pipe
|
| Read bytes
v
Process B
Socket-based Flow
Process A
|
| Serialize and send message
v
Socket or Network Stack
|
| Receive and deserialize
v
Process B
IPC requires a defined protocol, message format, error policy and ownership model. Shared memory can reduce copying, but shared updates still require synchronization.
Thread Communication
Threads can communicate through objects in their shared process memory.
Shared Queue
+---------------------------------------+
| Work Item 1 | Work Item 2 | Work Item 3 |
+---------------------------------------+
^ |
| v
Producer Thread Consumer Thread
Shared memory makes communication convenient, but unsynchronized access can produce:
- Race conditions
- Lost updates
- Inconsistent reads
- Memory-visibility defects
- Deadlocks
- Data corruption
Shared-memory rule: Shared data should have an explicit ownership and synchronization policy. The fact that threads can access an object does not mean they can access it safely at the same time.
Concurrency vs Parallelism
| Concept | Meaning |
|---|---|
| Concurrency | Several tasks make progress during overlapping periods |
| Parallelism | Several tasks execute at the same instant on separate execution resources |
Single-core Concurrency
Time -------------------------------------------------->
CPU Core:
Thread A | Thread B | Thread A | Thread C | Thread B
The operating system rapidly switches among runnable threads. Tasks overlap in progress, but only one thread executes on the core at a given instant.
Multi-core Parallelism
Time -------------------------------------------------->
CPU Core 1:
Thread A | Thread A | Thread A | Thread A
CPU Core 2:
Thread B | Thread B | Thread B | Thread B
Different threads can execute simultaneously when separate cores are available and the operating system schedules the threads accordingly.
Context Switching
A context switch occurs when execution changes from one schedulable task to another.
The operating system may need to preserve or restore:
- Program counter
- CPU registers
- Stack pointer
- Scheduling information
- Memory-management context where applicable
Switching between threads in the same process can require less memory- management work than switching between separate processes, but actual cost depends on the operating system, processor and execution state.
Excessive switching can consume capacity through:
- Scheduling overhead
- Reduced cache locality
- Translation-cache disruption
- Lock handoff
- Interrupted instruction pipelines
Blocking
A thread blocks when it cannot continue until an event occurs.
Common blocking operations include:
- Waiting for file or network I/O
- Waiting for a lock
- Waiting for another thread
- Waiting on a condition variable
- Sleeping until a timer expires
Thread A:
Read from network
|
v
Waiting for data
Thread B:
Continues processing another task
With operating-system-managed threads, one blocked thread does not necessarily prevent another runnable thread in the same process from being scheduled.
POSIX Thread Example
The following example uses the POSIX threads interface. POSIX threads are not part of the ISO C standard.
#define _POSIX_C_SOURCE 200809L
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
struct ThreadInput
{
int first;
int last;
long long result;
};
static void *sum_range(
void *argument)
{
struct ThreadInput *input =
argument;
input->result = 0;
for (int value = input->first;
value <= input->last;
++value) {
input->result += value;
}
return NULL;
}
int main(void)
{
struct ThreadInput first = {
.first = 1,
.last = 500000,
.result = 0
};
struct ThreadInput second = {
.first = 500001,
.last = 1000000,
.result = 0
};
pthread_t first_thread;
pthread_t second_thread;
int status =
pthread_create(
&first_thread,
NULL,
sum_range,
&first
);
if (status != 0) {
fprintf(
stderr,
"Unable to create "
"the first thread.\n"
);
return EXIT_FAILURE;
}
status =
pthread_create(
&second_thread,
NULL,
sum_range,
&second
);
if (status != 0) {
pthread_join(
first_thread,
NULL
);
fprintf(
stderr,
"Unable to create "
"the second thread.\n"
);
return EXIT_FAILURE;
}
if (pthread_join(
first_thread,
NULL) != 0) {
return EXIT_FAILURE;
}
if (pthread_join(
second_thread,
NULL) != 0) {
return EXIT_FAILURE;
}
long long total =
first.result +
second.result;
printf(
"Total: %lld\n",
total
);
return EXIT_SUCCESS;
}
Compile on a POSIX Platform
cc -std=c17 -Wall -Wextra -Wpedantic -pthread thread_sum.c -o thread_sum
Program Analysis
- Two independent work ranges are created.
- Each thread receives a pointer to a different input structure.
- The threads write to separate result fields.
pthread_join()waits for each thread to finish.- The main thread combines results only after both worker threads complete.
- No two threads intentionally update the same result object.
Shared-data Race Example
static int counter = 0;
static void *increment_counter(
void *argument)
{
(void)argument;
for (int index = 0;
index < 100000;
++index) {
++counter;
}
return NULL;
}
The increment is a read-modify-write operation. Concurrent execution can cause lost updates, and the unsynchronized conflicting accesses produce a data race.
static int counter = 0;
static pthread_mutex_t counter_mutex =
PTHREAD_MUTEX_INITIALIZER;
static void *increment_counter(
void *argument)
{
(void)argument;
for (int index = 0;
index < 100000;
++index) {
if (pthread_mutex_lock(
&counter_mutex) != 0) {
return NULL;
}
++counter;
if (pthread_mutex_unlock(
&counter_mutex) != 0) {
return NULL;
}
}
return NULL;
}
The mutex ensures that only one participating thread executes the protected update at a time. A production design should also define how synchronization failures are reported and whether a different aggregation approach reduces contention.
Linux Process Creation Example
The following example uses POSIX process interfaces and is not ISO C.
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void)
{
pid_t child =
fork();
if (child == -1) {
perror(
"fork"
);
return EXIT_FAILURE;
}
if (child == 0) {
printf(
"Child process: %ld\n",
(long)getpid()
);
return EXIT_SUCCESS;
}
printf(
"Parent process: %ld\n",
(long)getpid()
);
int child_status;
if (waitpid(
child,
&child_status,
0) == -1) {
perror(
"waitpid"
);
return EXIT_FAILURE;
}
if (WIFEXITED(child_status)) {
printf(
"Child exit status: %d\n",
WEXITSTATUS(
child_status
)
);
}
return EXIT_SUCCESS;
}
Compile
cc -std=c17 -Wall -Wextra -Wpedantic process_example.c -o process_example
After fork(), both processes continue from the return point,
but receive different return values. The child enters the child path, while
the parent waits for the child through waitpid().
Process Memory After Creation
On systems implementing fork(), parent and child begin with
logically separate process address spaces based on the parent's state.
Implementations commonly avoid copying every memory page immediately by
using copy-on-write techniques.
Immediately after fork:
Parent logical pages ----+
+---- Shared physical pages while unchanged
Child logical pages -----+
After one process writes:
Parent logical page ----------> Parent physical copy
Child logical page -----------> Child physical copy
Programs should rely on the documented process semantics rather than assuming one specific operating-system optimization.
Benefits of Multiple Processes
- Stronger memory isolation
- Independent permissions and identities
- Separate failure boundaries
- Independent deployment or restart
- Explicit communication contracts
- Ability to use different runtimes or implementations
Costs of Multiple Processes
- Separate address-space and resource overhead
- IPC design and serialization
- Additional data copying in some communication paths
- More complex lifecycle supervision
- Cross-process debugging and tracing
- Consistency challenges when state is duplicated
Benefits of Multiple Threads
- Efficient sharing of in-process data
- Concurrent request or task processing
- Parallel CPU execution on multiple cores
- Improved responsiveness when one thread waits for I/O
- Lower resource duplication than separate processes
Risks of Multiple Threads
- Race conditions
- Deadlocks
- Lock contention
- Memory-visibility problems
- Unsafe object lifetime
- Non-deterministic defects
- Process-wide impact from a fatal thread fault
Server Concurrency Models
| Model | Description | Main Consideration |
|---|---|---|
| Process per request or connection | A separate process handles each unit of work | Strong isolation but potentially high creation and memory cost |
| Thread per request or connection | A separate thread handles each unit of work | Simple flow but potentially large thread counts |
| Worker process pool | A fixed group of processes handles queued work | Balances isolation and controlled concurrency |
| Thread pool | A bounded set of reusable threads handles queued work | Requires safe shared state and queue management |
| Event-driven loop | A small number of threads coordinates nonblocking operations | Blocking work must not stall the event loop |
| Hybrid model | Several processes each use threads or event loops | Combines isolation with in-process concurrency |
Thread Pool
A thread pool creates a controlled number of worker threads and assigns queued tasks to those workers.
Incoming Work
|
v
Bounded Work Queue
|
+--------+--------+--------+
| | | |
v v v v
Worker 1 Worker 2 Worker 3 Worker 4
A useful thread-pool design defines:
- Minimum and maximum worker count
- Queue capacity
- Admission and rejection policy
- Task cancellation
- Shutdown behaviour
- Exception or error handling
- Metrics for active workers and queue delay
Queue rule: An unbounded task queue can hide overload temporarily while latency, memory use and backlog continue growing.
Choosing Thread Count
There is no universal optimal thread count. The useful value depends on:
- Available CPU cores
- CPU-bound vs I/O-bound work
- Time spent waiting
- Memory available for stacks and task state
- Lock contention
- Connection-pool capacity
- Downstream service limits
- Required latency and throughput
CPU-bound workloads often gain limited benefit after available cores are occupied. I/O-bound workloads can use additional concurrency while some threads wait, but dependency and memory limits still apply.
Failure Isolation
Separate Processes
Process A fails
|
+--> Process A terminates
Process B
|
+--> Can continue if shared dependencies remain healthy
Threads in One Process
Thread A causes a fatal memory fault
|
v
Containing process can terminate
|
+--> Thread B terminates
+--> Thread C terminates
Thread-level error handling can recover from ordinary task errors, but a fatal process-level memory or runtime failure can affect every thread in the process.
Shared State and Synchronization
Shared mutable state should be minimized and protected using an appropriate synchronization or ownership strategy.
Common approaches include:
- Mutexes
- Read-write locks
- Condition variables
- Semaphores
- Atomic operations
- Thread-local data
- Immutable data
- Message passing
- Single-owner state
The next course topics examine synchronization, race conditions and deadlocks in greater depth.
Linux Process and Thread Inspection
List Processes
ps -ef
Show Threads for a Process
ps -T -p PROCESS_ID
Interactive Thread View
top -H -p PROCESS_ID
Display Process Hierarchy
pstree -p
Inspect Process File Descriptors
ls -l /proc/PROCESS_ID/fd
Inspect Process Status
cat /proc/PROCESS_ID/status
Command availability and output can vary by Linux distribution, kernel and installed packages.
When to Prefer Processes
Processes are commonly considered when:
- Strong failure isolation is important
- Security permissions must differ
- Tasks use different runtimes
- Components need independent deployment or restart
- Shared-memory access should be limited
- An existing platform isolates workers by process
- Untrusted or unstable work requires containment
When to Prefer Threads
Threads are commonly considered when:
- Tasks need efficient access to shared in-process data
- One application performs several concurrent operations
- CPU work can run in parallel across cores
- I/O waiting should not block all application progress
- Process-level resource duplication would be undesirable
- A supported thread-safe runtime and library environment is available
Selection Questions
| Question | Why It Matters |
|---|---|
| How much state must be shared? | Threads share memory naturally, while processes need an explicit mechanism. |
| How important is failure isolation? | Separate process boundaries normally provide stronger containment. |
| Is the work CPU-bound or I/O-bound? | The useful concurrency model depends on where time is spent. |
| Does the runtime support safe parallel threads? | Language and library behaviour can limit effective thread parallelism. |
| What synchronization is required? | Shared mutable state introduces correctness and contention risks. |
| What are the memory limits? | Processes and threads both consume memory, including individual thread stacks. |
| How will tasks be supervised? | Failure detection, restart and shutdown affect operational reliability. |
| Which platform constraints apply? | Available APIs and process models vary across systems. |
Common Mistakes
Assuming a Process and Program Are the Same
A program is passive executable content. A process is a running environment created for program execution.
Assuming Threads Have Separate Heaps
Threads within one process normally share the process heap and global data.
Assuming Threads Share Stacks
Each thread requires its own stack and execution state.
Creating One Thread per Request Without Limits
Large thread counts can consume stack memory and increase scheduling, contention and context-switching overhead.
Accessing Shared Data Without Synchronization
Concurrent conflicting access can create races and undefined program behaviour.
Holding Locks During Slow I/O
Other threads can remain blocked while one thread waits for storage or a network dependency.
Assuming More Threads Always Improve Performance
Useful concurrency is limited by CPU cores, memory, locks, connections and downstream capacity.
Ignoring Process Communication Failures
IPC mechanisms require framing, validation, timeout, error and lifecycle handling.
Using Shared Memory Without Ownership Rules
Shared memory between processes has many of the same synchronization risks as shared memory between threads.
Assuming Separate Processes Cannot Fail Together
Processes can share the same kernel, host, storage, network, configuration and external dependencies.
Recommended Test Cases
| Test | Expected Evidence |
|---|---|
| Single-thread baseline | Latency, throughput and resource usage are recorded |
| Increasing thread count | The useful concurrency limit and saturation point are identified |
| Worker process failure | The supervisor detects and handles the failed process |
| Thread task failure | The error is contained according to the task policy |
| Shared-counter stress test | The final value remains correct under repeated execution |
| Queue overload | The bounded admission or rejection policy is applied |
| Graceful shutdown | Workers stop without abandoning work outside the documented policy |
| Blocked dependency | Timeout and cancellation behaviour prevent indefinite resource use |
| Process communication failure | The failure is detected and reported without corrupting message state |
Best Practices
Recommended Practices
- Use processes when resource and failure isolation are primary requirements.
- Use threads when concurrent tasks benefit from controlled in-process sharing.
- Keep shared mutable data limited and clearly owned.
- Protect every shared invariant with a documented synchronization strategy.
- Use bounded worker pools instead of unlimited creation.
- Measure useful concurrency under a representative workload.
- Consider stack memory when selecting thread limits.
- Avoid holding locks during slow disk or network operations.
- Define timeouts for blocking and remote work.
- Design IPC messages as explicit contracts.
- Validate all data received across process boundaries.
- Define startup, shutdown, cancellation and cleanup behaviour.
- Measure queue delay, active workers and rejected work.
- Use platform-supported synchronization primitives.
- Test race-sensitive code repeatedly and with appropriate diagnostic tools.
- Document whether platform-specific APIs such as POSIX threads are required.
Practice Exercise
Design a file-processing application that reads several input files, transforms their records and writes one output file.
Tasks
- Design a version using one process and one thread.
- Design a version using one process and a bounded thread pool.
- Design a version using several worker processes.
- Identify which memory is shared in each design.
- Identify the communication mechanism used by the process-based design.
- Define who owns each input and output buffer.
- Define how output ordering is preserved.
- Define how worker failure is detected.
- Define graceful shutdown behaviour.
- Compare throughput, memory usage and failure isolation.
Model Comparison
| Design | Main Benefit | Main Risk |
|---|---|---|
| Single thread | Simple state and deterministic control flow | Limited parallelism and blocked progress during slow operations |
| Thread pool | Efficient in-process sharing and controlled concurrency | Synchronization, contention and process-wide failure impact |
| Worker processes | Stronger isolation and independent recovery | IPC, serialization and higher resource overhead |
Frequently Asked Questions
What is a process?
A process is an operating-system-managed execution environment with an independent virtual address space and assigned resources.
What is a thread?
A thread is an independently schedulable execution path within a process.
What do threads in one process share?
They commonly share code, global data, heap memory, address space and process-level resources such as open files.
What does each thread keep separately?
Each thread has its own program counter, register state, stack and scheduling state.
Can threads run in parallel?
Yes. Separate runnable threads can execute simultaneously when multiple processor cores and the operating-system scheduler permit it.
Why are processes more isolated?
Separate processes normally have protected virtual address spaces and cannot directly access each other's private writable memory.
Why is thread communication faster?
Threads can directly access shared in-process objects without first serializing them through a process boundary. Safe concurrent access can still require synchronization.
Can one thread crash the entire process?
A fatal memory, runtime or process-level error triggered by one thread can terminate the process and its other threads.
Are threads always cheaper than processes?
Threads normally reuse more process resources, but their total cost still depends on stack allocation, scheduling, synchronization, runtime and workload behaviour.
Should every server use one thread per request?
No. The appropriate model depends on request behaviour, connection count, memory, CPU cores, blocking operations and dependency limits.
Can processes share memory?
Yes, through explicit shared-memory facilities. Access must still follow synchronization and ownership rules.
What comes after processes vs threads?
The next topic is synchronization, followed by race conditions and deadlocks.
Key Takeaway
A process provides a resource environment and protection boundary, while a thread provides an independently schedulable execution path inside that process. Separate processes offer stronger isolation but require explicit communication. Threads share memory efficiently but introduce races, synchronization needs and process-wide failure risks. Choose the model according to isolation, communication, workload, memory, failure recovery and operational requirements, then validate the design under a representative concurrent workload.