G27
Self-checkedTier 1 11 min reados.threads

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 threadShared by all threads of a process
thread idcode (text)
program counterglobal and static data
registersheap
stack (local variables, return addresses)open files, sockets
state (running, ready, blocked)address space and page table
signal mask, thread-specific datasignal 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):

ModelMappingProperties
Many-to-onemany user threads to one kernel threadcheap; one blocking call blocks all; no parallelism on multiple cores
One-to-oneeach user thread has its own kernel threada blocking call blocks only its thread; real parallelism; the number of threads is limited by the cost of kernel threads (Linux, Windows)
Many-to-manymm user threads over n≤mn \le m kernel threadsflexible, a blocking call does not block the others, parallelism up to nn; 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 ff of the work can be done in parallel by TT threads on TT cores, the speedup is bounded by Amdahl's law, 1/((1−f)+f/T)1/((1 - f) + f/T) (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
running T1(thread T1 executing)
PC
5004
SP
63000
R0
7
PTBR
7000
running
T1
addrdecimalmeaning
064-TCB of T1: PC
065-TCB of T1: SP
066-TCB of T1: R0
067runningTCB of T1: state
06E5120TCB of T2: PC
06F61000TCB of T2: SP
07040TCB of T2: R0
071readyTCB of T2: state
12C7shared 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).

1 / 9
computed by script: an interpreter or machine simulation, one action per step. python3 scripts/gen-os-proc-traces.py

Worked examples

EX 1Shared global data, private stacks

Easy

Two 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: 1

Reading 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?

Easy

Classify 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?

Medium

A 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

Medium

A program spends 75% of its time in a loop that parallelises perfectly, the rest is serial. On 4 cores: speedup =1/(0.25+0.75/4)=1/0.4375=2.29= 1/(0.25 + 0.75/4) = 1/0.4375 = 2.29. On unlimited cores the limit is 1/0.25=41/0.25 = 4. 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 level

A 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 TT cores with parallel fraction ff: 1/((1−f)+f/T)1/((1 - f) + f/T).

Traps and GATE patterns

Connections

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

GATE 2024 · MSQQ7 · page 296 of the book
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
GATE 2023 · MSQQ12 · page 297 of the book
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
GATE 2021 · MSQQ18 · page 298 of the book
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.
GATE 2017 · Set 1 · MCQ · 1 markQ25 · page 299 of the book
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
GATE 2017 · Set 2 · MCQ · 1 markQ26 · page 299 of the book
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
GATE 2014 · MCQQ34 · page 300 of the book
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
GATE 2011 · MCQ · 1 markQ42 · page 302 of the book
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.
GATE 2007 · MCQ · 1 markQ48 · page 303 of the book
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 answer

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 answer

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 answer

1/(0.2+0.8/8)=1/0.3=3.331/(0.2 + 0.8/8) = 1/0.3 = 3.33.

Question 4

Why is a switch between two threads of the same process cheaper than between two processes?

Show answer

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 1/((1−f)+f/T)1/((1 - f) + f/T).

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