A thread is the smallest sequence of instructions that an operating system can schedule independently. Each thread belongs to a process and has its own program counter, registers and stack, while it shares the process’s address space, open files and other resources with every other thread of the same process. In concurrency and programming generally, threads are the basic mechanism that lets one program make progress on several tasks at once.
Because threads share memory, they communicate cheaply by reading and writing the same data, but that same sharing is the source of the classic concurrency hazards: race conditions, deadlocks and visibility problems. Understanding a thread therefore means understanding both what it gives, concurrent and potentially parallel execution, and what it demands, disciplined synchronization of shared state.
How a thread executes inside a process
A thread executes when the operating system scheduler assigns it to a CPU core, runs it for a time slice and later suspends it to run another thread. Switching between threads is a context switch: the kernel saves the registers and program counter of the outgoing thread and restores those of the incoming one.
The table shows which resources a thread owns privately and which it shares with the other threads of its process.
| Resource | Private or shared | Consequence |
|---|---|---|
| Program counter and registers | Private | Each thread follows its own path through the code |
| Call stack | Private | Local variables are safe from other threads by default |
| Thread-local storage | Private | Per-thread copies of selected variables |
| Heap and global variables | Shared | Data visible to all threads; needs synchronization |
| Open files and sockets | Shared | Any thread can use or close a descriptor |
| Address space and code | Shared | Creating a thread is cheaper than creating a process |
A thread moves through a small set of states: new, runnable, running, blocked or waiting, and terminated. A thread that waits for disk, network or a lock is blocked and consumes no CPU time, which is why threads let a program keep working while some tasks wait for input and output.
Kernel threads, user threads and virtual threads
Threads differ mainly in who schedules them and how much they cost. Most runtimes today map each language-level thread to one kernel thread, but several modern platforms add lightweight threads that the runtime multiplexes onto a small pool of kernel threads.
The following table compares the main thread models with typical characteristics; exact figures depend on the operating system, runtime and configuration.
| Model | Scheduled by | Typical initial stack | Practical scale |
|---|---|---|---|
| Kernel thread (1:1) | Operating system | About 1 MB on Windows, often 8 MB of virtual space on Linux | Thousands per process |
| Green thread (N:1) | Language runtime on one kernel thread | Small, runtime-defined | Many, but no parallelism |
| Goroutine (M:N) | Go runtime | About 2 KB, grows on demand | Hundreds of thousands or more |
| Java virtual thread (M:N) | JVM, since Java 21 | Small, heap-allocated stack chunks | Millions in suitable workloads |
Stack memory for kernel threads is usually reserved as virtual address space and committed only as it is used, so the reserved size overstates real memory consumption. The practical limit comes from scheduler overhead, memory for actually used stacks and context-switch cost, which is typically in the range of one to several microseconds plus the indirect cost of cold CPU caches.
Threads compared with processes and asynchronous code
A thread is one of several ways to structure concurrent work, and the right choice depends on isolation needs and the type of workload. Processes give stronger isolation, while asynchronous programming handles very many waiting tasks with few threads.
The table contrasts threads with the main alternatives.
| Aspect | Thread | Process | Asynchronous task |
|---|---|---|---|
| Memory | Shared within the process | Isolated by default | Shared, usually on one event loop |
| Creation cost | Moderate | High | Very low |
| Failure impact | A crash can end the whole process | Contained to one process | An unhandled error can stall the loop |
| Parallel execution | Yes, on multiple cores | Yes | Not by itself |
| Best fit | CPU-bound work with shared data | Isolation and fault containment | Many concurrent input and output waits |
Threads provide concurrency, meaning overlapping lifetimes of tasks, and on multicore hardware they also provide parallelism, meaning simultaneous execution. A notable exception is standard CPython, where the global interpreter lock lets only one thread execute Python bytecode at a time; free-threaded builds without that lock are available in recent versions but are not yet the default.
Synchronization and thread safety
Code is thread-safe when it behaves correctly no matter how the scheduler interleaves the threads that call it. Achieving this requires controlling access to shared mutable state with synchronization primitives and respecting the memory model of the language, which defines when a write by one thread becomes visible to another.
- Mutex or lock: allows only one thread at a time into a critical section.
- Read-write lock: permits many concurrent readers or one exclusive writer.
- Semaphore: limits the number of threads that may use a resource simultaneously.
- Condition variable: lets a thread sleep until another thread signals a change.
- Atomic operations: update a single variable indivisibly without a full lock.
- Immutable data and message passing: avoid shared mutable state altogether.
Most production systems do not create a thread per task but use a thread pool, a fixed or bounded set of worker threads that take tasks from a queue. For CPU-bound work, a pool size close to the number of cores is a common starting point; for blocking work, larger pools help, but each additional thread adds memory and switching overhead.
Common threading bugs and how to prevent them
Threading bugs are hard to find because they depend on timing and are often not reproducible. A program can pass thousands of test runs and fail under production load, which makes prevention by design more effective than debugging after the fact.
The table lists the most frequent problems with their cause and a standard remedy.
| Problem | Cause | Prevention |
|---|---|---|
| Race condition | Unsynchronized access to shared data | Locks, atomics or confinement of data to one thread |
| Deadlock | Threads wait for each other’s locks in a cycle | Acquire locks in a fixed global order; use timeouts |
| Starvation | A thread never obtains the resource it needs | Fair locks and bounded waiting |
| Visibility error | A write stays in a cache or register | Proper synchronization or volatile and atomic types |
| Thread leak | Threads are created but never finish | Pools with bounded size and clear shutdown |
The most reliable strategy is to minimize shared mutable state. Data that only one thread touches needs no locking, and immutable data can be shared freely; locks should then protect the small remainder and be held for as short a time as possible.
The short version
A thread is like one cook in a shared kitchen. A restaurant can have several cooks working at the same time, each following a different recipe, but they all use the same fridge, counters and pans.
That sharing makes the kitchen efficient, because nobody needs a separate set of equipment. It also creates conflicts: two cooks grabbing the same pan at once can ruin both dishes. Programs with several threads face the same problem, so they use rules about who may touch what and when. The key point is that threads let a program do several things at once, as long as the shared work is carefully coordinated.
Where it makes a difference
A desktop photo editor applies a filter to a 40-megapixel image. In the first version, the filter runs on the same thread that draws the user interface, so the window freezes for about eight seconds and the operating system marks the application as not responding.
The developers move the filter to a background worker thread and split the image into horizontal strips processed by a thread pool with one worker per core. On an eight-core laptop the filter finishes in roughly one and a half seconds rather than eight, short of a perfect eightfold speedup because of memory bandwidth and coordination overhead. The interface thread stays free to show a progress bar and a cancel button.
The only shared data is the output image, and each worker writes to its own strip, so no locks are needed for the pixel data. A single atomic counter tracks completed strips for the progress bar. The result is a responsive application and a roughly fivefold faster filter.