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.
| Resource | Process | Thread |
|---|---|---|
| Virtual address space | Own | Shared with siblings |
| Heap and globals | Own | Shared |
| Stack and registers | Own | Own — one per thread |
| File descriptors | Own table | Shared |
| Signal handling | Per process | Delivered to one thread |
| Creation cost | Higher (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).
niceandreniceadjust scheduling priority; on Linux, cgroups andionicecontrol far more.- Runaway thread counts cause more time in the scheduler than in your code — measure before adding concurrency.
How processes talk
| Mechanism | Shape | Best for |
|---|---|---|
| Pipes / FIFOs | Byte stream, one direction | Parent-child pipelines |
| Unix domain sockets | Bidirectional stream or datagram | Local services, low latency |
| Shared memory | A mapped region visible to both | Large buffers, highest throughput |
| Signals | Asynchronous notification | Termination, reload, supervision |
| Message queues | Discrete messages with framing | Decoupling, 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 = SIGTERMSignals 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?
multiprocessing, a C extension that releases the GIL, or a runtime without one.What is a zombie process?
wait().Related
Memory and virtual memory Files, permissions and I/O
Last refreshed 2026-09-18.