Threads and multithreading models
Threads as units of execution inside a process: what threads share and what is private, why they are cheaper than processes, user-level and kernel-level threads, the many-to-one, one-to-one and many-to-many models, blocking behaviour and multicore speedup.
Why it exists
A process bundles two things: a private address space and resources (memory, open files), and a thread of control that executes in it. Many programs want several activities at once that work on the same data: a web server serving many clients, a word processor that checks spelling while you type. Creating a process for each activity is expensive and makes sharing data awkward. A thread separates the two ideas: one process can have several threads, each with its own execution state but sharing the process's memory and resources. Threads are cheaper to create and switch and communicate through shared memory. GATE asks what is shared and private, the thread models and what blocks, and the speedup from threads.
Prerequisites
You need the process, its address space and its PCB from the process model.
Precise definitions
Thread (lightweight process). A basic unit of CPU utilisation: a thread has its own thread id, program counter, set of registers, stack and state, and shares with the other threads of its process the code, the global (static) data, the heap, the open files and other resources, and (usually) the signal handlers. A traditional process is a process with one thread.
| Private to each thread | Shared by all threads of a process |
|---|---|
| thread id | code (text) |
| program counter | global and static data |
| registers | heap |
| stack (local variables, return addresses) | open files, sockets |
| state (running, ready, blocked) | address space and page table |
| signal mask, thread-specific data | signal handlers, process id |
The kernel or the thread library keeps a thread control block (TCB) per thread: the registers, the stack pointer and the state; the PCB holds the shared resources.
Benefits. Responsiveness (one thread can continue while another blocks); resource sharing (the threads share memory without the kernel's help); economy (creating and switching a thread costs much less than for a process: no new address space, and no TLB flush on a switch between the threads of one process); scalability (threads can run in parallel on several cores).
Costs. Threads share data without protection from each other, so one buggy thread can corrupt the others and updating shared data needs synchronization (critical sections). A crash in one thread usually kills the whole process.
User-level and kernel-level threads.
- User-level threads are created and scheduled by a library in user space; the kernel sees only one process. Creating and switching them needs no system call and is very fast, and the library can use its own scheduling policy. But if one thread makes a blocking system call, the whole process blocks (all its threads), and the threads cannot run in parallel on several processors, since the kernel schedules the process as one.
- Kernel-level threads are created and scheduled by the kernel, which keeps a TCB for each. A blocking call blocks only that thread, and the threads can run on different processors at the same time. Creation and switching cost more (a system call and a kernel switch), though still less than for processes.
Multithreading models (how user threads map to kernel threads):
| Model | Mapping | Properties |
|---|---|---|
| Many-to-one | many user threads to one kernel thread | cheap; one blocking call blocks all; no parallelism on multiple cores |
| One-to-one | each user thread has its own kernel thread | a blocking call blocks only its thread; real parallelism; the number of threads is limited by the cost of kernel threads (Linux, Windows) |
| Many-to-many | user threads over kernel threads | flexible, a blocking call does not block the others, parallelism up to ; complex |
A variant, the two-level model, allows binding a user thread to a kernel thread as well.
Thread libraries. POSIX threads (pthread_create, pthread_join), Java threads and Windows threads. pthread_create starts a new thread running a given function; pthread_join waits for it to finish.
Multicore speedup. If a fraction of the work can be done in parallel by threads on cores, the speedup is bounded by Amdahl's law, (Amdahl's law). The serial part limits it however many threads are used.
How it works
The animation shows a switch between two threads of the same process. Each thread has a TCB (rows 100 to 103 and 110 to 113); the data they share, a counter at address 300, has just one copy. Only the PC, SP and one register are saved and loaded. The page table base register keeps the same value, so neither the TLB nor the cache contents are disturbed.
A switch between two threads of the same process: step 1
Machine run, not typed(thread T1 executing)| addr | decimal | meaning | |
|---|---|---|---|
| 064 | - | TCB of T1: PC | |
| 065 | - | TCB of T1: SP | |
| 066 | - | TCB of T1: R0 | |
| 067 | running | TCB of T1: state | |
| 06E | 5120 | TCB of T2: PC | |
| 06F | 61000 | TCB of T2: SP | |
| 070 | 40 | TCB of T2: R0 | |
| 071 | ready | TCB of T2: state | |
| 12C | 7 | shared counter (global data) |
Step 1
Two threads, T1 and T2, belong to ONE process. They share the code, the global data (here a counter, address 300, value 7), the heap, the open files and the page table (PTBR = 7000). Each has its own PC, registers and stack, saved in a thread control block (TCB).
Worked examples
EX 1Shared global data, private stacks
EasyTwo threads each compute a value in a local variable and store it into a global array.
#define _POSIX_C_SOURCE 200809L
#include <pthread.h>
#include <stdio.h>
static int shared[2]; /* global data: one copy, seen by every thread */
static void *shared_seen[2]; /* the address of `shared` as each thread sees it */
static void *local_addr[2]; /* the address of each thread's own local variable */
static void *worker(void *arg) {
int id = *(int *)arg;
int local = 10 * (id + 1); /* lives on this thread's own stack */
shared_seen[id] = (void *)shared;
local_addr[id] = (void *)&local;
shared[id] = local;
return NULL;
}
int main(void) {
pthread_t t[2];
int ids[2] = {0, 1};
for (int i = 0; i < 2; i++) pthread_create(&t[i], NULL, worker, &ids[i]);
for (int i = 0; i < 2; i++) pthread_join(t[i], NULL);
printf("global array after both threads: %d %d\n", shared[0], shared[1]);
printf("both threads saw the same global array: %d\n", shared_seen[0] == shared_seen[1]);
printf("their local variables are at different addresses: %d\n", local_addr[0] != local_addr[1]);
return 0;
}global array after both threads: 10 20
both threads saw the same global array: 1
their local variables are at different addresses: 1Reading it. The global array has one address and both threads wrote into it, so the main thread sees both values after pthread_join. Each thread's local lives on its own stack, so the two have different addresses. (If the threads had updated the same element, the result would depend on timing: see critical sections.)
EX 2Shared or private?
EasyClassify each: code of the program (shared); a global variable (shared); a local variable of a function the thread is running (private, on its stack); the program counter (private); the heap (shared, though each thread may hold different pointers into it); open file descriptors (shared); the register values (private); the stack pointer (private). A pointer to a heap block stored in a global variable is visible to every thread; stored in a local variable it is held privately, but the block it points to is still shared.
EX 3Which model blocks?
MediumA process has 4 user threads. One makes a blocking read. Many-to-one: the single kernel thread blocks, so all 4 threads stop, though 3 could have run. One-to-one: only the kernel thread of the reader blocks; the other 3 keep running. Many-to-many with 2 kernel threads: the reader blocks its kernel thread; the library's other kernel thread continues to run the other threads (and the library can create another kernel thread if all are blocked). With many-to-one, even on a 4-core machine all 4 threads share one core. This is the standard way to decide a question about "which model allows true parallelism" (one-to-one and many-to-many) and "which one blocks everything on a system call" (many-to-one).
EX 4Speedup from threads
MediumA program spends 75% of its time in a loop that parallelises perfectly, the rest is serial. On 4 cores: speedup . On unlimited cores the limit is . A thread library that creates a thread per element of a large array gains nothing beyond the number of cores and loses time creating them: the number of threads should be about the number of cores for compute-bound work. If the work waits on I/O, more threads than cores can still help, as the waiting threads do not use a core.
EX 5Process or thread?
GATE levelA server must handle 1000 concurrent clients that all read from one shared in-memory table. With a process per client every process has its own copy of the address space (copy-on-write on fork), shared access to the table needs shared memory or messages, creation costs a fork each, and a switch changes the page table and flushes the TLB. With a thread per client the table is simply a shared global; creation and switching are cheaper; a blocking read in one client blocks only that thread (with kernel threads). The price: the threads must synchronize their updates of the table, and one thread's bug can crash the server. The opposite choice, processes, is better when isolation matters more than sharing (a crash of one client must not affect the others, for example a web browser's tabs).
Cheat box
Cheat box
- A thread has its own id, PC, registers, stack. Threads of one process share code, global data, heap, open files, address space.
- Threads are cheaper to create and switch than processes; communication through shared memory; they need synchronization.
- User-level threads: library only, very fast, one blocking call blocks the whole process, no multicore parallelism.
- Kernel-level threads: kernel schedules them; blocking blocks one thread; parallel on several cores; slower to create.
- Many-to-one: blocks all, no parallelism. One-to-one: parallel, limited by kernel thread cost. Many-to-many: flexible.
- Speedup on cores with parallel fraction : .
Traps and GATE patterns
Connections
Builds on
Across subjects
A thread is a lighter unit than the process and the cost difference appears in the context switch: no change of address space. A fork copies an address space; creating a thread does not. Threads that update shared data need critical sections, and the speedup they give is limited by the serial fraction.
PYQs
8 previous year questions from your book. I re-solved each one myself (0 by running or simulating, 8 by reasoning only); the book's key is shown next to my answer. All PYQs
Which of the following statements about threads is/are TRUE?
- (a)Threads can only be implemented in kernel space
- (b)Each thread has its own file descriptor table for open files
- (c)All the threads belonging to a process share a common stack
- (d)Threads belonging to a process are by default not protected from each other
Which one or more of the following need to be saved on a context switch from one thread (T1) of a process to another thread (T2) of the same process ?
- (a)Page Table base register
- (b)Stack pointer
- (c)Program Counter
- (d)General purpose register
Consider the following multi-threaded code
segment (in a mix of C and pseudocode), invoked
by two processes P1 and P2, and each of the
processes spawns two threads T1 and T2:
int x = 0; // global
Lock L1; // global
main() {
create a thread to execute foo(); // Thread T1
create a thread to execute foo(); // Thread T2
wait for the two threads to finish execution;
print (x);}
foo () {
int y = 0;
Acquire L1;
x = x + 1;
y = y + 1;
Release L1;
print (y);}
Which of the following statement(s) is / are correct?- (a)At least one of P1 and P2 will print the value of x as 4.
- (b)At least one of the threads will print the value of y as 2.
- (c)Both T1 and T2, in both the processes, will print the value of y as 1.
- (d)Both P1 and P2 will print the value of x as 2.
Threads of a process share
- (a)global variables but not heap
- (b)heap but not global variables
- (c)neither global variables nor heap
- (d)both heap and global variables
Which of the following is/are shared by all the threads in a process? I. Program counter II. Stack III. Address space IV. Registers
- (a)(I) and (II) only
- (b)(III) only
- (c)(IV) only
- (d)(III) and (IV) only
Which one of the following is FALSE?
- (a)User level threads are not scheduled by the kernel.
- (b)When a user level thread is blocked, all other threads of its process are blocked.
- (c)Context switching between user level threads is faster than context switching between kernel level threads
- (d)Kernel level threads cannot share the code segment. OPERATING SYSTEMS
A thread is usually defined as a "light weight process" because an operating system (OS) maintains smaller data structures for a thread than for a process. In relation to this, which of the following is TRUE?
- (a)On per-thread basis, the OS maintains only CPU register state.
- (b)The OS does not maintain a separate stack for each thread.
- (c)On a per-thread basis, the OS does not maintain virtual memory state.
- (d)On a per thread basis, the OS maintains only scheduling and accounting information.
Consider the following statements about user level threads and kernel level threads. Which one of the following statements is FALSE?
- (a)Context switch time is longer for kernel level threads than for user level threads.
- (b)User level threads do not need any hardware support.
- (c)Related kernel level threads can be scheduled on different processors in a multi-processor system.
- (d)Blocking one kernel level thread blocks all related threads. OPERATING SYSTEMS
Self-check
Question 1
Which of these are private to a thread: global variable, stack, program counter, heap, open file table?
Show answerHide
Stack and program counter (and the registers). The global variable, heap and open file table are shared.
Question 2
A process with many-to-one threads runs on an 8-core machine. How many cores can it use at once?
Show answerHide
One, since all its user threads map to a single kernel thread.
Question 3
80% of a program parallelises, run with 8 threads on 8 cores. Speedup?
Show answerHide
.
Question 4
Why is a switch between two threads of the same process cheaper than between two processes?
Show answerHide
The address space does not change: no page-table base reload and no TLB flush, and the cache contents remain useful.
Revision card
One-screen revision
- Private: id, PC, registers, stack. Shared: code, globals, heap, open files.
- Cheaper than processes; need synchronization.
- User-level: fast, blocks all, no parallelism. Kernel-level: parallel, blocks one.
- Many-to-one, one-to-one, many-to-many.
- Speedup .
References
- ·Silberschatz, Galvin, Gagne, Operating System Concepts, Chapter 4 Threads and Concurrency (overview, multicore programming, multithreading models, thread libraries)
- ·Tanenbaum and Bos, Modern Operating Systems, Chapter 2 Processes and Threads (threads)
- ·Stallings, Operating Systems: Internals and Design Principles, Chapter 4 Threads, SMP and Microkernels
Verification
The shared global data and private stacks are demonstrated by the real pthreads program on this page (npm run verify:c). The switch animation is a run of a teaching machine (scripts/gen-os-proc-traces.py) re-checked by tests/os-proc-traces.test.ts: only the PC, SP and R0 are exchanged and the page table base register never changes. The speedup arithmetic is Amdahl's law (scripts/enum-checks/os_proc.py checks the page numbers). Which items are 'shared' follows the usual POSIX/textbook list (code, global data, heap, open files, signal handlers; private: registers, PC, stack, thread-specific data, signal mask); details differ between systems. A thread's behaviour after fork or exec in a multithreaded process is system dependent and only mentioned. Textbook chapter titles are from memory. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand. No defect found. Status raised to self-checked; verified needs a textbook or human check.
last verified: never
My notes (saved in this browser)