Threads
What a thread is, what it shares and what it owns, why threads beat processes for many jobs, and how user threads map onto kernel threads in the many-to-one, one-to-one, many-to-many, and two-level models.
What you will be able to do
- Define a thread and list exactly what is private to a thread versus shared across a process.
- Explain the four benefits of multithreading with a concrete example for each.
- Contrast user-level and kernel-level threads on creation cost, blocking behaviour, and multiprocessor use.
- Describe the many-to-one, one-to-one, many-to-many, and two-level models with their example systems.
- Work out how many threads can truly run in parallel given a model, kernel thread count, and core count.
What a thread is
A lightweight process: its own program counter, registers, and stack, but shared everything else.
A thread, sometimes called a lightweight process (LWP), is the basic unit of CPU utilisation. It comprises a thread ID, a program counter, a register set, and a stack. It shares with the other threads of the same process the code section, the data section, and other OS resources such as open files. A traditional heavyweight process has a single thread of control. Give it several and it can do more than one task at a time.
Per-thread versus per-process
| Private to each thread | Shared by all threads in the process |
|---|---|
| Thread ID | Code section (the program text) |
| Program counter | Data section (globals, heap) |
| Register set | Open files and other OS resources |
| Stack (local variables, return addresses) | Address space and memory mapping |
A multithreaded process has three parts
- Address space: the code and data of the process.
- Threads: individual execution contexts.
- Resources: the physical support needed to run, such as memory and disk.
1#include <pthread.h>2#include <stdio.h>34int shared_counter = 0; /* data section: visible to every thread */56void *worker(void *arg) {7 int local = *(int *)arg; /* on THIS thread's stack: private */8 shared_counter += local; /* touches the shared data section */9 return NULL;10}1112int main(void) {13 pthread_t t1, t2;14 int a = 1, b = 2;15 pthread_create(&t1, NULL, worker, &a);16 pthread_create(&t2, NULL, worker, &b);17 pthread_join(t1, NULL);18 pthread_join(t2, NULL);19 printf("%d\n", shared_counter);20 return 0;21}
Output
3 (usually; the unsynchronised += is a race, which is Lecture 9's problem)
Four benefits of threads
Responsiveness, resource sharing, economy, and multiprocessor utilisation.
The four benefits from the slides
- 1
Responsiveness
A multithreaded interactive application keeps running even if part of it is blocked or doing a lengthy operation. The browser keeps rendering while another thread waits on the network.
- 2
Resource sharing
Processes can only share through shared memory or message passing, which the programmer must arrange explicitly. Threads share the memory and resources of their process by default, so several threads of activity can live in one address space.
- 3
Economy
Allocating memory and resources for a new process is costly. Threads are inexpensive to create, destroy, and represent. With so little context, switching between threads is much faster.
- 4
Utilisation of multiprocessor architectures
A single-threaded process can run on only one CPU no matter how many exist. Multithreading on a multi-CPU machine increases concurrency: each thread may run in parallel on a different processor.
What a thread needs space for
Must store per thread
- Program counter (PC)
- Stack pointer (SP)
- General-purpose registers
Does not need per thread
- Shared memory information
- Information about open files and I/O devices
- Code
- Data
Process versus thread, multiprocessing versus multithreading
Same goal, concurrency. Very different price tags.
Process versus thread
| Aspect | Process | Thread |
|---|---|---|
| Weight | Heavyweight | Lightweight |
| Address space | Own, isolated | Shared with sibling threads |
| Creation and termination cost | High: memory, PCB, resources | Low: only PC, SP, registers, stack |
| Context switch | Slow: address space changes | Fast: no address space change |
| Communication | IPC needed: shared memory or message passing, set up explicitly | Shared data by default |
| Protection | One process cannot corrupt another | One thread can corrupt another's data |
| If one blocks | Others unaffected | Depends on the threading model (see below) |
Multiprocessing versus multithreading
Multiprocessing
- Many processes, each with its own address space
- Isolation and robustness: a crash stays contained
- Expensive to create and switch
- Example: a browser that runs each tab as a separate process
Multithreading
- Many threads inside one process, one address space
- Cheap creation, fast switching, easy data sharing
- One bad thread can bring down the process
- Example: a word processor's UI, keystroke, and spellcheck threads
User-level and kernel-level threads
Who knows the thread exists: a library in user space, or the kernel?
Support for threads can be provided at the user level, giving user threads, or by the kernel, giving kernel threads. The distinction is about who creates, schedules, and manages them.
User-level threads
- Supported above the kernel and implemented by a thread library in user space.
- The library handles creation, scheduling, and management with no kernel support.
- The kernel is unaware of them. Everything happens in user space without kernel intervention.
- Consequences: no kernel resources allocated per thread, and switching does not change the address space, so they are fast to create and manage.
Kernel-level threads
- Supported and managed by the kernel. Creation, scheduling, and management happen in kernel space.
- Generally slower to create and manage than user threads.
- If one thread makes a blocking system call, the kernel can schedule another thread of the same application.
- On a multiprocessor, the kernel can schedule threads of one process on different processors.
User-level versus kernel-level threads
| User-level threads | Kernel-level threads | |
|---|---|---|
| Managed by | Thread library in user space | The operating system kernel |
| Kernel awareness | Kernel does not know they exist | Kernel schedules each one |
| Creation and management | Easier and faster | Slower |
| Thread switch | No kernel-mode privilege or mode switch needed | Requires a mode switch to kernel mode |
| Blocking system call | Blocks the entire process | Only that thread blocks; kernel runs another |
| Multiprocessor use | Cannot exploit multiple CPUs | Threads of one process run on different CPUs |
| Portability | Run on any operating system | Depend on kernel support |
| Kernel routines | Not applicable | Kernel routines themselves can be multithreaded |
Multithreading models
How user threads map onto kernel threads decides what blocks and what runs in parallel.
The models, one by one
- 1
Many-to-one (user-level threading)
Many user threads mapped to a single kernel thread. Efficient because management is in user space. Shortcoming: the entire process blocks if any thread makes a blocking system call, and since only one thread can access the kernel at a time, threads cannot run in parallel on multiprocessors. Used on systems without kernel thread support. Examples: Solaris Green Threads, GNU Portable Threads.
- 2
One-to-one (kernel-level threading)
Each user thread maps to its own kernel thread. More concurrency than many-to-one: when one thread blocks, another runs. Multiple threads run in parallel on multiprocessors. Shortcoming: creating a kernel thread for every user thread is overhead that can burden the application. Examples: Windows 95/98/NT/2000, OS/2.
- 3
Many-to-many (hybrid)
Multiplexes many user threads onto a smaller or equal number of kernel threads. The OS creates a sufficient number of kernel threads. Suffers from neither shortcoming of the other two: developers create as many user threads as they like, the kernel threads run in parallel on a multiprocessor, and when one blocks the kernel schedules another. Examples: IRIX, HP-UX, Solaris 8.
- 4
Two-level
A popular variation of many-to-many that also allows a user thread to be bound to a specific kernel thread. Examples: IRIX, HP-UX, Tru64 UNIX, Solaris 8 and earlier.
Models compared
| Model | Kernel threads | One thread blocks | Parallel on multicore | Cost | Examples |
|---|---|---|---|---|---|
| Many-to-one | 1 | Whole process blocks | No | Very low | Solaris Green Threads, GNU Portable Threads |
| One-to-one | One per user thread | Only that thread | Yes | High per thread | Windows 95/98/NT/2000, OS/2 |
| Many-to-many | Fewer than or equal to user threads | Kernel runs another | Yes, up to kernel thread count | Moderate | IRIX, HP-UX, Solaris 8 |
| Two-level | Pool plus bound threads | Kernel runs another | Yes | Moderate | IRIX, HP-UX, Tru64 UNIX, Solaris 8 and earlier |
Before the exam
The lines worth memorising, and the mistakes that lose marks.
Remember this
- 1Thread = thread ID + program counter + register set + stack. Shares code, data, open files with its process.
- 2Lightweight process (LWP) is another name for a thread; a traditional process is heavyweight with one thread of control.
- 3Four benefits: responsiveness, resource sharing, economy, utilisation of multiprocessor architectures.
- 4Thread switch is faster because the address space does not change. Only PC, SP, and general registers move.
- 5Threads do not give memory protection between each other. Processes do.
- 6A single-threaded process uses one core no matter how many exist.
- 7User-level threads: library-managed, kernel unaware, fast to create, no mode switch, but a blocking call blocks the whole process and no multiprocessor use.
- 8Kernel-level threads: kernel-managed, slower, mode switch per switch, but a blocked thread lets siblings run and threads spread over CPUs.
- 9Many-to-one: one kernel thread, whole process blocks, no parallelism. Solaris Green Threads, GNU Portable Threads.
- 10One-to-one: kernel thread per user thread, parallel, but creation overhead. Windows 95/98/NT/2000, OS/2.
- 11Many-to-many: user threads multiplexed over a smaller or equal pool of kernel threads; neither shortcoming. IRIX, HP-UX, Solaris 8.
- 12Two-level: many-to-many plus the option to bind a user thread to a kernel thread. Adds Tru64 UNIX to the list.
- 13Max parallel threads = min(kernel threads, cores). 4 kernel threads on 2 cores gives 2.
Exam traps
- Listing the stack as shared. Each thread has its own stack; the heap and globals in the data section are shared.
- Saying a thread has its own code section. Code is shared; only the program counter into that code is private.
- Claiming user-level threads can run in parallel on multiple CPUs. The kernel sees one schedulable entity, so no.
- Thinking a blocking system call in a kernel-level thread blocks the process. Only that thread blocks; the kernel schedules another.
- Picking one-to-one as 'best balance'. The slides give that to many-to-many; one-to-one pays a kernel thread per user thread.
- Confusing concurrency with parallelism. Many-to-one has concurrency but never parallelism.
- Counting kernel threads instead of cores when asked how many threads run at once. The answer is the smaller of the two.
- Listing memory protection as a benefit of threads. Shared memory is the point; protection is what you give up.
Quiz yourself
One question at a time with instant feedback. Your best score is saved.
Which component is unique to each thread?
Not quite
A thread owns its thread ID, program counter, register set, and stack. Code, data, and open files belong to the process and are shared.
Finished reading?
Mark this lecture as done
Take the quiz above first so your score is saved here.