AlgoMaster Logo

Processes vs Threads

14 min readUpdated August 7, 2026
Listen to this chapter
Unlock Audio

A backend server needs to handle several requests at the same time. It could run several worker processes, several threads inside one process, or a combination of both.

All of those workers can execute application code. All can be scheduled on CPUs. The crucial difference is not whether they can run concurrently.

The difference is the boundary around their state:

A process is primarily a resource ownership and protection boundary. A thread is primarily a scheduling and execution boundary.

A process provides an address space and access to operating-system resources. A thread provides one path of execution through that environment. Every ordinary process begins with at least one thread, and a multithreaded process contains several independently schedulable threads.

Process vs. Thread Responsibilities

A process represents one running application environment. It gives the kernel a boundary within which to manage memory, open resources, identity, permissions, and lifetime.

A thread represents one instruction stream. It has the CPU state and scheduling information needed to execute independently.

The relationship looks like this:

The process does not execute instructions without a thread. Its threads do not each need a separate copy of the entire process environment.

This separation lets the kernel answer two different questions:

QuestionAnswered by
Which memory and resources may this application use?The process
Which instruction stream should run next?The thread

In a single-threaded process, the two boundaries appear to coincide. There is one resource environment and one execution context. Once a process has multiple threads, the distinction becomes visible.

Two Processes: Separate Application Environments

Suppose two instances of the same server program are running:

Nothing crosses between the two processes. That separation is the default, and sharing anything between these processes takes deliberate work.

The processes may execute identical machine instructions, but their ordinary writable memory is logically separate. If Process 5100 changes a global variable, that change does not alter Process 5200's copy.

The virtual memory system enforces this separation. An address valid in one process does not grant that process permission to access memory at the same numerical address in another process.

Separate processes also have separate resource tables and process identities. One process can change its current directory, install a signal handler, or close a file descriptor without directly changing the corresponding table entry or setting in an unrelated process.

Separate does not mean that processes can never refer to the same underlying object. Processes can deliberately share memory, inherit access to the same open file or socket, or communicate through kernel facilities. Those relationships are explicit exceptions built on top of the default process boundary.

For example, two server processes may both have a descriptor for one listening socket:

Their descriptor tables remain separate even though entries in both tables reach the same socket. Closing the descriptor in Process A does not remove Process B's descriptor.

Two Threads: Separate Execution, Common Environment

Now consider one process with two threads:

The address space, the resource table, and the process settings exist once. The CPU state, stack usage, and scheduling state exist per thread.

The threads can execute different functions, block on different events, and run on different CPUs. At the same time, ordinary reads and writes by both threads use the same process address space.

If Thread 6100 updates a global object on the heap, Thread 6101 can observe the same object. No interprocess transfer is required because both address translations lead into one address space.

The shared environment extends beyond memory. Ordinary threads in one process use common process-wide resources such as the file-descriptor table. If one thread opens a file, the returned descriptor is available to the other threads. If one thread closes that descriptor, it disappears from the common table and is no longer valid for the others.

This direct sharing is useful, but it also increases coupling. A correct update can communicate immediately. An incorrect pointer, accidental descriptor close, or corrupted process-wide setting can also affect every thread in the process.

What Threads Share

Ordinary threads in the same process share the process's virtual address space. This includes:

  • Program code
  • Global and static variables
  • Heap allocations
  • Memory mappings
  • Shared libraries loaded into the process

If one thread stores a value in a heap object and another thread accesses that object, both operations target the same memory.

Threads also normally share process-wide kernel-managed state, including:

  • The file-descriptor table
  • The current working directory and root directory
  • The file-creation mask
  • Signal dispositions
  • Process credentials as presented through the POSIX process interface
  • Resource limits and other process-wide settings

This is why a thread can accept a connection using a listening socket opened by another thread. Both use the same descriptor table; there is no need to copy the socket into the second thread.

Shared access does not guarantee sensible results when operations overlap. If two threads update the same data without the coordination required by that data, the outcome may depend on timing. The sharing boundary makes direct communication possible, but it does not make concurrent access automatically safe.

What Each Thread Keeps Private

Threads share an environment, not an execution context. Each thread needs its own:

  • Thread ID
  • Instruction pointer and CPU register state
  • Stack pointer and normal user-space stack region
  • Kernel stack
  • Scheduling state, runtime accounting, and CPU affinity
  • Signal mask and thread-directed pending signals
  • Thread-local-storage state

The separate register state lets two threads execute the same function with different arguments and intermediate values.

The separate stacks let them maintain independent function-call chains:

Two call chains exist at the same instant in the same process. Neither can see the other's frames, which is why local variables need no coordination.

Thread A can return from normalize_path() without changing Thread B's return path. Each thread's stack pointer refers to its own current call frame.

Thread-local storage provides variables with one instance per thread even though the threads otherwise share an address space. The familiar C errno value behaves as thread-local state on a multithreaded POSIX system; an error in one thread must not overwrite the error value being inspected by another.

Calling these items private requires one qualification. A thread's user-space stack is intended for that thread's execution, but it is still mapped into the common process address space. Another thread with a valid pointer can read or write that stack memory. The isolation is a programming convention, not a memory-protection boundary.

Kernel stacks are different. They live in protected kernel memory and are managed per task so that each thread can enter and wait inside the kernel independently.

The Boundary at a Glance

The comparison becomes clearer when ownership and execution are considered separately:

PropertySeparate processesThreads in one process
Virtual address spaceSeparate by defaultShared
Global and heap dataSeparate by defaultShared
File-descriptor tableSeparate, though entries may refer to common objectsShared
Process identityDifferent PIDCommon process PID; distinct TID per thread
CPU register stateSeparate for every thread in each processSeparate for every thread
User-space stackSeparate address spaces and stacksDistinct stack regions in one address space
Scheduling statePer threadPer thread
Ordinary memory communicationRequires an explicit shared or transfer mechanismDirectly available through shared memory
Memory protection from each otherEnforced by defaultNot provided within the shared address space
Typical failure boundaryOne processUsually the entire process

The table contains an important symmetry: CPU register and scheduling state are per thread in both designs. A worker process with one thread and a worker thread inside a larger process are both represented by schedulable execution contexts.

The major asymmetry is in the resources those contexts can reach.

Loading simulation...

A Memory Visibility Demonstration

The following Linux program performs two sequential experiments with one global variable.

First, a child process changes the variable and exits. The parent retains its original value because the processes have logically separate writable memory.

Then a new thread changes the variable. The initial thread observes the changed value because both threads use the same address space.

Compile and run it:

A run produces:

The process experiment is sequential: the parent waits for the child before reading the variable. The thread experiment is also sequential: the initial thread joins the worker before reading. The result is therefore about the memory boundary, not a race between simultaneous updates.

An operating system can optimize process creation by initially reusing physical memory pages until either process writes to them. The visible rule remains that an ordinary write in one process does not change the other process's writable memory.

The virtual address printed for global_number might even be numerically identical in the parent and child. That does not prove the memory is shared. Virtual addresses are interpreted within an address space, and the same number in two processes can resolve according to different memory mappings.

Blocking and Parallel Execution

Processes and threads can both provide concurrent execution.

Four single-threaded processes give the scheduler four runnable threads. One process containing four threads also gives the scheduler four runnable threads:

Both arrangements give the scheduler four threads to choose from. The difference is what those threads can reach, not how many of them there are.

If four logical CPUs are available, either design may execute four instruction streams simultaneously.

The difference appears when one instruction stream blocks. If a thread waits for storage or a socket, that thread becomes non-runnable. Other runnable threads can continue whether they belong to the same process or to different processes.

This corrects two common oversimplifications:

  • Multiple processes are not required for parallel CPU execution.
  • Multiple threads do not automatically create parallel execution.

Parallel execution requires multiple runnable threads and multiple CPUs available to run them. A process can contain those threads, or the threads can belong to different processes.

Communication and Coordination

Threads communicate naturally through the memory they already share. One thread can place work in a heap object, and another can read that same object.

The lack of a transfer step makes shared-memory communication direct, but it creates a correctness obligation. The threads must coordinate access when the data can be modified concurrently. Without correct coordination, valid operations can interleave into an invalid result.

Separate processes do not see one another's ordinary heap and globals. They communicate by deliberately using operating-system facilities or explicitly shared memory.

The process boundary therefore changes the default:

Neither default is universally better. Direct access can make cooperation simple and efficient when all workers belong to one trusted application. Explicit communication can make ownership and failure boundaries easier to reason about.

Communication between processes is also not necessarily slow, and communication between threads is not necessarily cheap. Performance depends on message size, access patterns, contention, data copying, kernel involvement, and hardware behavior. The boundary describes semantics first; measurements determine cost for a particular workload.

Failure Isolation

The process boundary is enforced by virtual memory protection. If Process A follows a bad pointer, it cannot ordinarily overwrite Process B's private heap.

If a process crashes because of an invalid memory access, other independent processes normally continue. A supervisor can restart the failed process while the others keep serving work.

Threads do not have that protection from one another. All threads use the same address space, so one thread can corrupt memory used by another. A fatal memory fault in any thread normally terminates the entire process, ending all of its threads.

ArrangementWhen one unit fails fatally
Separate worker processesWorker A crashes; workers B and C keep running
Threads in one worker processThread A's fatal fault ends the process, so threads B and C end too

Process isolation is not absolute. Processes may share files, sockets, shared-memory regions, or an external database. One process can still damage shared data through those channels. Processes also depend on the same kernel and hardware.

The narrower claim is the useful one:

Separate processes provide a default memory-protection and lifetime boundary that threads in one process do not.

This boundary is valuable for untrusted code, privilege separation, fault containment, and components that should be restarted independently.

Resource Use and Overhead

Threads are commonly called “lightweight” because adding a thread can reuse an existing address space and process-wide resource structures. A new thread still requires real resources:

  • Kernel bookkeeping and a kernel stack
  • A user-space stack
  • Thread-local state
  • Scheduler and accounting state

Stack space deserves particular attention. A runtime may reserve a substantial virtual address range for each thread even when only a small part has physical memory committed. A process with a very large number of threads can therefore consume meaningful memory and address-space capacity.

A new process needs a distinct address-space identity and separate resource containers. That does not imply that every byte is immediately duplicated. Operating systems can share read-only program pages and use delayed copying for writable memory while preserving process isolation.

The same caution applies to switching. A switch between threads in one process may avoid some address-space work required when switching between processes, but both are scheduler-visible context switches. The actual cost depends on the kernel, processor, and working set.

It is therefore too broad to claim that threads are always faster than processes. A more useful statement is:

Threads usually share more existing state; processes usually provide stronger default separation.

The best choice follows from which state should be common, which failures should be contained, and what the workload actually costs on the target system.

Backend Design Example

Consider a service with four request workers and an in-memory cache.

In a multithreaded design, all four workers can use one cache in the shared heap:

A value inserted by one thread is directly available to the others. The application must make concurrent cache access safe. A memory-corruption bug can damage the shared cache or the process itself.

In a multiprocess design, each worker has a private heap:

Four copies of the same data, each warmed separately.

The private caches may duplicate data and can diverge unless the design adds an explicit way to keep them consistent. In return, corrupting cache A does not directly overwrite cache B. The workers can also be terminated and restarted independently.

The worker processes may still inherit or receive access to one listening socket, so separate memory does not prevent them from serving the same network endpoint.

A production service can combine the models: several isolated processes, each containing several threads. The outer process boundary limits the impact of failures, while threads within each process share local state. The result inherits both sets of tradeoffs rather than eliminating them.

Choosing the Boundary

Threads are a natural fit when several execution paths belong to one trusted application and need frequent access to the same in-memory structures. They avoid making every worker maintain a separate copy of that state.

Processes are a natural fit when components need stronger memory isolation, different credentials, independent restart behavior, or a clear ownership boundary. They make communication more explicit, which can be a benefit when uncontrolled sharing would be difficult to reason about.

The choice should begin with concrete questions:

  • Should a write to ordinary memory be visible to every worker?
  • Should closing a file descriptor affect every worker?
  • Should one memory-safety failure terminate all workers?
  • Must workers use different permissions?
  • Can the workers be restarted independently?
  • Is duplicated per-process state acceptable?

These questions expose the real design decision. “Threads are fast” and “processes are safe” are slogans; the required sharing and isolation boundaries are the engineering facts.

Loading simulation...

Summary

A process defines a resource, protection, and lifetime boundary. A thread defines an independently schedulable path of execution within that boundary.

Separate processes have distinct address spaces and resource containers by default, which provides memory and failure isolation. Threads in one process share memory and process-wide resources, making direct cooperation possible while allowing one thread's mistakes to affect the whole process.

Both processes and multithreaded processes can use multiple CPUs. The practical choice is not “concurrency or no concurrency,” but which state should be shared and where isolation should exist.

Quiz

Processes vs Threads Quiz

5 quizzes