Definition
A thread is the smallest unit of execution within a process. It is a lightweight process that shares the same memory space and resources with other threads of the same process, but has its own independent execution path (program counter and stack). Multiple threads can run concurrently within a single process, allowing true parallelism on multi-core systems and better responsiveness on single-core systems.
Thread vs Process
| Feature | Process | Thread |
|---|---|---|
| Definition | Independent program in execution | Unit of execution within a process |
| Memory Space | Separate (isolated) | Shared (same process) |
| Creation Time | Slow (allocate memory, PCB, etc.) | Fast (no new memory allocation) |
| Context Switch | Slow (save/load full context) | Fast (only save/load registers) |
| Communication | Difficult (via IPC mechanisms) | Easy (shared memory) |
| Synchronization | Complex (need IPC) | Simple (mutex, semaphore) |
| Crash Impact | Independent | May crash entire process |
| Resource Sharing | Cannot share easily | Share memory, files, handles |
| Example | Multiple instances of Word | Multiple tabs in browser |
Thread Components
Each Thread Has Its Own
- Program Counter (PC): Where thread is executing
- Register Set: Current values of CPU registers
- Stack: Local variables, function parameters, return addresses
- Thread State: Running, Ready, Waiting
- Thread ID: Unique identifier within process
- Scheduling Information: Priority, CPU burst time
All Threads Share
- Code Section: Same executable instructions
- Data Section: Global and static variables
- Heap: Dynamically allocated memory
- Open Files: File handles, sockets
- Process Resources: I/O devices, network connections
Benefits of Multithreading
1. Responsiveness
Single-Threaded Application:
Main thread handling UI events
User clicks button
↓
Start long operation
↓
UI freezes for 5 seconds
↓
Operation completes, UI unfreezes
Multi-Threaded Application:
Main thread: Handles UI events
Worker thread: Handles long operation
User clicks button → Worker starts operation → UI stays responsive
2. Resource Sharing
All threads in process share:
- Memory (no need to copy data between processes)
- Open files (read/write same file)
- Network connections
- Database connections
Benefit: Efficient resource usage
3. Faster Execution
On multi-core systems:
- Multiple threads run simultaneously on different cores
- True parallelism (not just concurrent)
- Can actually speed up execution 2x, 4x, 8x (depends on cores)
4. Scalability
Server applications with thousands of clients:
- One thread per client instead of one process per client
- Threads use much less memory
- Create/destroy threads faster
- Better scalability
Types of Threads
1. User-Level Threads (User Threads)
Managed By: User-level thread library (in application)
Examples:
- Java threads (via JVM)
- POSIX pthreads in user mode
- Python threads (via threading module)
Advantages:
- Fast Creation: No kernel involvement
- Fast Context Switching: Switch between threads very quickly
- Fast Synchronization: Locks/semaphores fast
- Portable: Works same way across OS
Disadvantages:
- Kernel Doesn’t Know: OS scheduler doesn’t see threads
- No True Parallelism: On single-core, only one thread runs at a time
- Blocked I/O: If one thread blocks, entire process blocks
- No Multicore Usage: Can’t use multiple CPU cores efficiently
Blocking Problem Example:
Thread 1: Waiting for disk I/O (blocked in kernel)
Thread 2: Ready to run
Thread 3: Ready to run
Problem: Entire process blocked even though Thread 2 and 3 are ready!
2. Kernel-Level Threads (Kernel Threads)
Managed By: Operating System kernel
Examples:
- Windows threads
- Linux threads (since Linux 2.6)
- POSIX pthreads with kernel support
Advantages:
- OS Knows About Threads: Scheduler can run different threads on different cores
- True Parallelism: Multiple threads run simultaneously on multi-core
- Individual Blocking: One thread blocks, others continue
- Multicore Support: Can use all CPU cores
Disadvantages:
- Slower Creation: Need kernel system call
- Slower Context Switching: More overhead than user threads
- More Resource Usage: Each thread needs kernel resources
- Scheduling Overhead: OS must schedule each thread separately
3. Hybrid Threads (M:N Model)
Idea: Combine advantages of both user and kernel threads
How it works:
- N user-level threads mapped to M kernel-level threads
- User threads created/managed fast
- When user thread blocks, kernel thread available
- Can use multiple cores
Example:
- Create 1000 user threads (fast)
- Map to 4 kernel threads (run on 4 cores)
- Good balance of efficiency and parallelism
Multithreading Models
1. Many-to-One (N User → 1 Kernel)
┌──────────────────────────────┐
│ Process │
│ ┌────┐ ┌────┐ ┌────┐ │
│ │ U1 │ │ U2 │ │ U3 │ │ (User threads)
│ └────┘ └────┘ └────┘ │
│ ↓ ↓ ↓ │
│ ┌──────────────────────┐ │
│ │ Kernel Thread 1 │ │ (Kernel thread)
│ └──────────────────────┘ │
└──────────────────────────────┘
Characteristics:
- All user threads share one kernel thread
- Blocking one user thread blocks all others
- No true parallelism on multi-core
Used In: Green threads (old Java), some embedded systems
2. One-to-One (1 User → 1 Kernel)
┌──────────────────────────────┐
│ Process │
│ ┌────┐ ┌────┐ ┌────┐ │
│ │ U1 │ │ U2 │ │ U3 │ │ (User threads)
│ └──┬──┘ └──┬──┘ └──┬──┘ │
│ ↓ ↓ ↓ │
│ ┌────┐ ┌────┐ ┌────┐ │
│ │ K1 │ │ K2 │ │ K3 │ │ (Kernel threads)
│ └────┘ └────┘ └────┘ │
└──────────────────────────────┘
Characteristics:
- Each user thread has dedicated kernel thread
- True parallelism on multi-core
- Blocking one thread doesn’t block others
- More overhead (one kernel thread per user thread)
Used In: Windows, Linux with modern pthreads, Java (with OS threads)
3. Many-to-Many (M User → N Kernel)
┌────────────────────────────────┐
│ Process │
│ ┌───┐┌───┐┌───┐┌───┐┌───┐ │
│ │U1 ││U2 ││U3 ││U4 ││U5 │ │ (5 User threads)
│ └───┘└───┘└───┘└───┘└───┘ │
│ ↓ ↓ ↓ ↓ ↓ ↓ ↓ │
│ ┌─────────┬─────────┐ │
│ │ K1 │ K2 │ │ (2 Kernel threads)
│ └─────────┴─────────┘ │
└────────────────────────────────┘
Characteristics:
- N user threads can map to M kernel threads
- Flexibility: Can create many user threads, efficient scheduling with fewer kernel threads
- If user thread blocks, other user threads can run on same kernel thread
- Can utilize all CPU cores
Used In: Solaris, newer implementations
Thread Lifecycle
Thread States
- New: Thread created but not yet started
- Runnable: Thread ready to run (in ready queue)
- Running: Thread currently executing on CPU
- Waiting: Thread waiting for event (I/O, lock, notification)
- Terminated: Thread finished execution
State Transitions
New
↓
Runnable ← start()
↓ ↑
Running
↓
Waiting ← wait for event
↓
Running
↓
Terminated
Creating Threads
C Language (POSIX pthreads)
#include <pthread.h>
void* thread_function(void* arg) {
printf("Thread running\n");
return NULL;
}
int main() {
pthread_t thread_id;
pthread_create(&thread_id, NULL, thread_function, NULL);
pthread_join(thread_id, NULL); // Wait for thread
return 0;
}
Java
class MyThread extends Thread {
public void run() {
System.out.println("Thread running");
}
}
public class Main {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // Start thread
t.join(); // Wait for thread
}
}
Advantages of Multithreading
- Responsiveness: UI stays responsive during long operations
- Resource Sharing: Threads share memory (efficient)
- Parallelism: Can run on multiple cores simultaneously
- Scalability: Handle many clients with threads instead of processes
- Simplicity: Threads simpler to create than processes
- Performance: Better CPU utilization (while one thread waits for I/O, another runs)
Disadvantages
- Synchronization: Race conditions, deadlocks if not careful
- Complexity: Multithreaded code harder to debug
- Context Overhead: Context switching between threads has overhead
- Scalability Limits: Too many threads → excessive context switching
- Shared State Issues: Threads sharing memory can cause subtle bugs
- Testing: Hard to test multithreaded programs reliably
Synchronization Primitives
Since threads share memory, must synchronize access:
1. Mutex (Mutual Exclusion Lock)
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&lock);
// Critical section - only one thread here
shared_variable++;
pthread_mutex_unlock(&lock);
2. Semaphore
sem_t semaphore;
sem_init(&semaphore, 0, 1);
sem_wait(&semaphore); // Wait if semaphore is 0
// Critical section
sem_post(&semaphore); // Signal
3. Condition Variable
pthread_cond_t condition = PTHREAD_COND_INITIALIZER;
pthread_cond_wait(&condition, &lock); // Wait for signal
// ... in another thread ...
pthread_cond_signal(&condition); // Wake up waiter
Common Issues
1. Race Condition
int counter = 0;
// Thread 1 Thread 2
counter++; counter++;
// Result: counter = 1 or 2 (wrong!)
// Expected: counter = 2
// Solution: Use mutex
2. Deadlock
Thread 1: Thread 2:
lock(A) lock(B)
then lock(B) then lock(A)
// Both waiting forever!
3. Starvation
Lower priority thread never gets CPU time because high priority threads always running.
Summary
Threads are lightweight execution units within a process. They share memory and resources but can execute independently. User-level threads are fast but don’t utilize multi-core well. Kernel-level threads provide true parallelism but with more overhead. Multithreading is essential for modern responsive applications but requires careful synchronization to avoid race conditions and deadlocks. Understanding threads is critical for building responsive, scalable applications.