From process to thread: a second pair of hands
In the rungs behind you, you met the process — a program in action, with its own address space, its own list of open files, and one flow of control marching down the code, instruction after instruction. That marching flow is the part we now zoom in on. A thread is a single flow of control inside a process: a place in the code (where am I executing right now?), a set of CPU registers holding the values it is working with, and a stack recording the chain of function calls it has made. Up to now you have quietly assumed every process has exactly one such flow. A thread is what you get when a process has more than one.
Hold the kitchen picture from earlier guides: a process is a recipe being cooked. The single-threaded process is one cook following the recipe top to bottom. A multithreaded process is several cooks in the same kitchen, all working from the same recipe, sharing the same fridge and the same countertop — but each cook holds their own knife and stands at their own spot. The shared kitchen is what makes them a team rather than strangers; the separate spots are what let them work without bumping into one another every second. That single image — shared kitchen, private spots — is the whole idea of a thread, and the rest of this guide just makes it precise.
What is shared, and what is private
The defining fact about threads is the precise split between what siblings share and what each keeps to itself. All threads in a process share the one address space: the program's code (the text segment), its global and static data, and the heap where dynamically allocated objects live. They also share the process's open files and its other operating-system resources. So if one thread allocates an object on the heap, every sibling can see and touch it — that is exactly why passing data between threads is nearly free, and it is the source of both their power and their peril.
What each thread keeps private is just as important. Every thread has its own stack — the record of which functions it has called and the local variables living inside them — because each cook is partway through a different recipe step and must not scribble on another cook's notepad. Each thread also has its own CPU registers, including the program counter that says which instruction it will run next. That tiny private bundle (stack, registers, program counter) is precisely the state the kernel must save and restore when it switches the core from one thread to another. Everything else, the threads hold in common.
ONE PROCESS, TWO THREADS
+-------------------- shared by all threads --------------------+
| code (text) | globals / static data | heap |
| | | open files |
+---------------------------------------------------------------+
^ ^
| |
+-------------+ +-------------+
| THREAD 1 | private | THREAD 2 | private
| stack | | stack |
| registers | | registers |
| PC | | PC |
+-------------+ +-------------+Why bother? Four honest reasons
If threads bring danger, why use them at all? There are four classic reasons, and it helps to keep them distinct. First, responsiveness: while one thread waits on something slow — a disk read, a network reply — another thread can keep the program reacting to the user, so the window never freezes mid-download. Second, resource sharing: because threads already share the address space, they pass work to each other with no setup, unlike separate processes that need explicit channels. Third, economy: creating a thread is far cheaper in time and memory than creating a whole new process, because there is no new address space to build. Fourth, scalability: on a machine with several cores, separate threads can run on separate cores and genuinely speed up a big computation.
Notice that these reasons split cleanly into two families, and the split is so important it gets its own guide next. The first three — responsiveness, sharing, economy — are about concurrency: structuring a program as several flows that interleave so nobody gets stuck waiting. These pay off even on a single-core machine. The fourth — scalability — is about parallelism: actually running threads at the same instant, which needs real extra cores to deliver. Threads are the one mechanism that serves both families, which is exactly why they are everywhere. The next guide pulls these two ideas firmly apart, because confusing them causes more muddled thinking about threads than anything else.
Threads versus brand-new processes
You might object: the earlier rung already showed how to get more flows of control — call fork to make a second process. Why not just do that? Because a new process is a whole new kitchen. When the kernel forks, it must set up a fresh address space (in practice with the copy-on-write trick you met earlier, but it is still real bookkeeping), a fresh page table, a fresh set of file descriptors. And once forked, the two processes are isolated by design: to share data they must go through deliberate inter-process communication — pipes, shared-memory segments, messages. That isolation is a feature for safety, but it is overhead when what you actually want is cooperation.
Threads sit at the opposite corner of the trade-off. Spawning one adds just a stack and a register set to an address space that already exists — cheap and fast. And siblings cooperate by simply touching the same heap, no plumbing required. The contrast is the whole point of the thread-versus-process comparison: processes give you isolation at a higher cost, threads give you sharing at a lower cost. The price of that cheap sharing, of course, is that a bug in one thread can corrupt data the whole process relies on, and one thread that crashes can take its siblings down with it. There is no free lunch — only a different menu.
How you actually make one
In a real program you rarely poke the kernel directly; you call a thread library. On Linux and other Unix-like systems the standard one is POSIX threads, or pthreads. The shape is simple: you hand it a function and an argument, it starts a new flow of control running that function, and that flow runs concurrently with the one that created it. Later you can wait for it to finish and collect its result. The created thread shares everything in the process except its own stack and registers — which is just the shared-kitchen, private-spot picture, now expressed in code you can write.
- Write the work as a function, e.g. one that takes a chunk of an array and sums it.
- Call the library's create routine, passing that function plus the argument it should run on; this returns a handle and starts a new flow of control immediately.
- The new thread runs its function concurrently while the creating thread keeps going — both share the same heap, so the new thread can read the array directly with no copying.
- Call the library's join routine to block until the new thread finishes and to collect whatever it produced.
Creating and destroying threads over and over still costs something, so a common pattern reuses a fixed crew instead. A thread pool starts a handful of threads up front and feeds them a queue of tasks; when a thread finishes one task it grabs the next, so the crew stays busy without the churn of constant hiring and firing. Lighter-weight options exist too — green threads scheduled by a runtime instead of the kernel, and coroutines that hand control back and forth cooperatively — but those, the pool, and the deeper choice of how user threads map onto kernel threads are the subject of guide four. For now the headline stands: a thread is one more flow of control through a shared address space, and you make one with a single library call.
The shadow on the wall
Everything good about threads flows from sharing — and so does everything dangerous. Picture two cooks reaching for the same bowl of batter to add one egg each; if both check 'is there an egg in here yet?', both see no, and both crack an egg, the recipe quietly breaks. In code this looks like two threads each running 'count = count + 1' on a shared variable. That one line is really three machine steps — read the value, add one, write it back — and if the threads interleave at just the wrong moment, both read the same old number, both add one, both write back, and one increment vanishes. The result depends on timing, so it may pass every test and fail only in the wild.
This is the recurring villain of everything ahead, and we will name and tame it properly later — for now, just respect it. The moment two threads share mutable state without coordinating, correctness is no longer guaranteed. The whole stack of tools in the rungs to come — locks, semaphores, condition variables, monitors — exists for exactly one purpose: to let cooperating threads share a kitchen without spoiling each other's dishes. You have the foundation now: a thread is a flow of control inside a process, cheap because it shares, dangerous for the same reason. Next we sharpen the one distinction that everything else rests on: concurrency is not parallelism.