Processes and threads

What a process owns, what threads share, how the scheduler switches between them, and how processes cooperate.

What a process owns that a thread shares

A process is a running program with its own virtual address space. A thread is an execution context inside a process. Threads share the address space, which makes communication cheap and mistakes expensive.

ResourceProcessThread
Virtual address spaceOwnShared with siblings
Heap and globalsOwnShared
Stack and registersOwnOwn — one per thread
File descriptorsOwn tableShared
Signal handlingPer processDelivered to one thread
Creation costHigher (new page tables)Lower
ps -eLf | head            # one line per THREAD (-L), shows LWP column
ps -ef --forest          # process tree, reveals parent/child structure
cat /proc/self/status | grep -E 'Threads|VmRSS|VmSize'

Scheduling and context switches

The scheduler decides which runnable thread gets a core, and for how long. A context switch saves one thread's registers and restores another's; it costs microseconds of pure overhead and, more importantly, pollutes CPU caches.

# CPU-bound work parallelises across processes; threads do not help
import time, concurrent.futures as cf

def burn(n):
    t = time.perf_counter()
    while time.perf_counter() - t < 0.5:
        pass
    return n

# 4 cores: threads stay near 0.5s wall time only if the work releases the GIL
for kind, pool in (("thread", cf.ThreadPoolExecutor),
                   ("process", cf.ProcessPoolExecutor)):
    with pool(4) as ex:
        t0 = time.perf_counter()
        list(ex.map(burn, range(4)))
        print(kind, round(time.perf_counter() - t0, 2), "s")
  • I/O-bound work scales with threads: while one waits on a socket, another runs.
  • CPU-bound work in Python needs processes (or a native extension that releases the GIL).
  • nice and renice adjust scheduling priority; on Linux, cgroups and ionice control far more.
  • Runaway thread counts cause more time in the scheduler than in your code — measure before adding concurrency.
💡
Concurrency is not parallelism. Concurrency is having several tasks in flight; parallelism is executing several at the same instant. A single-core machine can be concurrent and never parallel, and that is often enough for I/O.

How processes talk

MechanismShapeBest for
Pipes / FIFOsByte stream, one directionParent-child pipelines
Unix domain socketsBidirectional stream or datagramLocal services, low latency
Shared memoryA mapped region visible to bothLarge buffers, highest throughput
SignalsAsynchronous notificationTermination, reload, supervision
Message queuesDiscrete messages with framingDecoupling, backpressure
# pipeline: two processes, one byte stream
grep -c ERROR /var/log/app.log

# a named pipe crossed by unrelated processes
mkfifo /tmp/demo && (echo hello > /tmp/demo &) && cat /tmp/demo

# send a signal by name instead of guessing the number
kill -HUP $(cat /run/app.pid)   # 1 = SIGHUP, 2 = SIGINT, 9 = SIGKILL, 15 = SIGTERM

Signals are notifications, not control flow: a handler runs at an arbitrary point in your program, so it must do almost nothing and hand real work back to the main loop.

FAQ

Why do threads not speed up my CPU-heavy Python code?
The GIL allows only one thread to execute bytecode at a time. Use multiprocessing, a C extension that releases the GIL, or a runtime without one.
What is a zombie process?
A process that has exited but whose parent has not yet read its exit status. It holds a slot in the process table — kill the parent, or fix it to call wait().

Memory and virtual memory Files, permissions and I/O

Last refreshed 2026-09-18.