Introduction
A thread is the basic unit of CPU utilization within a process. A traditional (heavyweight) process has a single thread of control, but modern applications are often multithreaded: a single process contains multiple threads that share the same address space yet execute independently, each with its own program counter, register set, and stack. Multithreading allows an application to perform multiple tasks concurrently — for example, a web server handling many client connections at once, or a UI application keeping its interface responsive while doing background work.
Cricket analogy: A thread is like one bowler in a bowling attack: the team (process) shares one ground and one scoreboard, but each bowler runs their own spell independently, tracking their own over count and figures.
Explanation
The key distinction between processes and threads is resource sharing. Separate processes typically do not share memory and require explicit inter-process communication (pipes, shared memory, sockets) to exchange data; creating a process (fork()) is relatively expensive because the OS must set up a new address space. Threads within the same process, by contrast, share the process's code, global/static data, heap, and open file descriptors, but each thread has its own stack, register set, and program counter. This makes threads much cheaper to create and switch between than processes, and it makes communication between threads trivial (they can read/write shared variables directly), at the cost of requiring explicit synchronization to avoid race conditions. Threads can be implemented at two levels: user-level threads are managed entirely by a thread library in user space without kernel involvement, making them extremely fast to create and switch, but the kernel is unaware of them, so if one user-level thread blocks on a system call, the entire process (all its threads) blocks; kernel-level threads are managed and scheduled directly by the OS kernel, so if one blocks, other threads in the process can still run, but creation and context switching are more expensive because they require kernel intervention. Most modern operating systems (Linux, Windows, macOS) use a one-to-one model where each user thread maps to a kernel thread, giving true parallelism on multicore systems while keeping the kernel aware of every thread's blocking behavior.
Cricket analogy: Kernel-level threads are like each fielder having their own line of communication to the umpire, so if one fielder is delayed retrieving the ball, others keep fielding; user-level threads are like only the captain talking to the umpire, so if the captain stalls, the whole team freezes.
Example
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
typedef struct {
int id;
long partial_sum;
} worker_arg_t;
void *sum_range(void *arg) {
worker_arg_t *w = (worker_arg_t *)arg;
long start = w->id * 1000000L;
long end = start + 1000000L;
long total = 0;
for (long i = start; i < end; i++) {
total += i;
}
w->partial_sum = total;
return NULL;
}
int main(void) {
const int NUM_THREADS = 4;
pthread_t threads[NUM_THREADS];
worker_arg_t args[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; i++) {
args[i].id = i;
args[i].partial_sum = 0;
if (pthread_create(&threads[i], NULL, sum_range, &args[i]) != 0) {
perror("pthread_create failed");
exit(EXIT_FAILURE);
}
}
long grand_total = 0;
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL); /* wait for each thread to finish */
grand_total += args[i].partial_sum;
}
printf("Grand total computed by %d threads: %ld\n", NUM_THREADS, grand_total);
return 0;
}Output
This program creates four threads, each summing a distinct range of one million numbers, then joins them and combines their partial sums into a grand total printed once. Because all four threads share the same process address space, they can write directly into the shared args array (each thread writes only to its own args[i].partial_sum, avoiding a race condition) without needing any inter-process communication mechanism. On a multicore machine, the kernel-level threads created by pthread_create() can run truly in parallel, finishing the computation roughly four times faster than a single-threaded version, illustrating the practical performance benefit of multithreading for CPU-bound, parallelizable work.
Cricket analogy: Four throwdown coaches each feeding balls to a different net simultaneously, then reporting their tally to the head coach who sums them into a total, is like four threads each summing a range and joining results into a grand total.
Key Takeaways
- A thread is the basic unit of CPU scheduling; a process can contain one or more threads.
- Threads in the same process share code, data, heap, and file descriptors, but each has its own stack, registers, and program counter.
- Threads are cheaper to create and switch between than processes because no new address space is needed.
- User-level threads are fast but block the whole process if one blocks on I/O; kernel-level threads avoid this but cost more to manage.
- Most modern OSes use a one-to-one threading model, mapping each user thread to a kernel thread for true parallelism.
Practice what you learned
1. What do threads within the same process share with each other?
2. Why are threads generally cheaper to create than processes?
3. What is a key drawback of pure user-level threads (managed entirely in user space)?
4. Which POSIX function is used to wait for a thread to finish execution?
Was this page helpful?
You May Also Like
What Is a Process?
Learn what a process is, how it differs from a program, and how the OS creates and manages processes.
Context Switching
Understand what happens when the CPU switches between processes and why it is pure overhead.
Introduction to Process Scheduling
Learn why the OS schedules processes, the queues involved, and the basic goals of a scheduler.