TT Lab
Get started
Learn Learning paths Courses

Operating Systems

Threads — Cheap Division, but Now You Have Something to Protect

Continue in TT Lab

In a nutshell

A thread is an execution flow that shares code, data, the heap, and open files, and keeps only its own stack and registers. That sharing is both the reason for its performance and the reason for its bugs.

Why this was needed

Processes provide isolation, but the price is expensive communication. Because each has a separate address space, exchanging data requires separate mechanisms such as pipes or shared memory. For programs like web servers, where many requests must look at the same data together, this cost is a burden.

Threads give up isolation and in exchange remove this cost. They use the same address space, so passing a single pointer is enough to share data.

How it works

Here is what the threads inside one process share and what each keeps for itself.

Shared Kept separately
Code area, global data, heap Stack
Open file descriptors Registers, program counter
Signal handlers, current directory Thread-local storage

This is why a thread context switch is cheaper than a process one. The address space is the same, so there is no need to swap the page table, and the TLB does not have to be flushed entirely.

Implementation models also split into three. The many-to-one model, managed only at user level, makes switches extremely cheap, but if one thread makes a blocking system call the whole process stops. The one-to-one model, adopted by Linux and Windows, maps each user thread to a kernel thread, which allows true parallel execution and independent blocking, but thread creation is relatively expensive. The many-to-many model is a compromise between the two, but its implementation is complex and it did not become mainstream. Today's goroutines and coroutines can be seen as an attempt to settle this debate again at the language runtime layer.

What it looks like in the field

Using threads brings a new responsibility: synchronization of shared data. Code in which two threads increment the same variable is, in machine code, three steps of read, add, and write, and if a switch slips in between them, one update is lost. This problem is hard to catch in tests and tends to appear only when the load rises.

One more thing. The intuition that "adding threads makes it faster" collapses once you exceed the number of CPU cores. More runnable threads than cores push each other out and only increase context switches and cache pollution. For CPU-bound work, a thread count near the number of cores is the starting point, and for I/O-bound work you can go higher to account for wait time. Copying a setting such as "thread pool size 200" without this distinction is a common cause of incidents.

What to check in the quiz that follows

Check whether you can tell what threads share and what they keep separately, and what bugs that choice invites.