COMP 3150Operating Systems

Module 2 · Unit 4 of 5

Threads

A thread is a unit of execution inside a process — a lighter-weight abstraction that shares code, data, and file descriptors with other threads in the same process. This unit examines why threads exist, how they are implemented, and how they change the way we structure concurrent programs.

5 lessons Lab 3 · POSIX threads CLO2 · CLO6 ≈ 1 week at recommended pace
Threads sharing an address space A single process containing three threads. Each thread has its own stack and registers, but all three share the process's code, data, heap, and open file descriptors. PROCESS · ONE ADDRESS SPACE Thread 1 stack registers own state Thread 2 stack registers own state Thread 3 stack registers own state shared SHARED BY ALL THREADS code data heap files one page table · one set of open file descriptors · one set of signal handlers

01 Unit learning outcomes

Upon completion of this unit you will be able to

  • U4.1 — Explain multithreading.
  • U4.2 — Compare threading models.
  • U4.3 — Analyse multicore concurrency.
  • U4.4 — Develop POSIX-thread programs.
  • U4.5 — Evaluate thread-management strategies.

This unit addresses course learning outcomes CLO2 and CLO6.

02 Lessons in this unit

The first lesson motivates threads. Lessons 4.2 and 4.3 contrast the two major implementation strategies. Lesson 4.4 examines how threads exploit multiple CPU cores. Lesson 4.5 gives you the POSIX API you will use throughout the course.

03 What you will learn, lesson by lesson

4.1 Why Threads?

  • Define a thread.
  • Explain why threads exist.
  • Compare threads and processes.
  • Identify thread benefits.
  • Analyse thread costs.

4.2 User Threads

  • Define user-level threads.
  • Describe library-based scheduling.
  • Explain blocking problems.
  • Analyse performance trade-offs.
  • Evaluate user-thread use cases.

4.3 Kernel Threads

  • Define kernel-level threads.
  • Describe kernel thread scheduling.
  • Compare threading models.
  • Analyse system-call costs.
  • Evaluate implementation strategies.

4.4 Multicore Processing

  • Explain true parallelism.
  • Describe cache coherence.
  • Analyse memory models.
  • Evaluate scaling limits.
  • Measure parallel speedup.

4.5 POSIX Threads

  • Create threads with pthread_create.
  • Join threads with pthread_join.
  • Manage thread attributes.
  • Handle thread errors.
  • Write correct multithreaded code.

04 Worked example

Two ways to compute a sum in parallel. Both compute the same result; they differ in cost.

Process-based

/* Each child computes a partial sum
   and returns it via a pipe. */
pid_t p1 = fork();
/* ... repeat for each chunk ... */
/* Parent reads each pipe and sums. */

fork duplicates the address space. Data must be marshalled through pipes or shared memory. Setup cost is high.

Thread-based

/* Each thread computes a partial sum
   and writes to shared memory. */
pthread_t t[N];
for (i = 0; i < N; i++)
    pthread_create(&t[i], NULL,
                   worker, &args[i]);
for (i = 0; i < N; i++)
    pthread_join(t[i], NULL);

The address space is shared. Results are exchanged through memory. Setup cost is low, but accesses to shared state must be synchronised.

Which is right for which workload?

  1. Processes are better when isolation matters. A crash in one process does not take down the others. A web server that forks a handler per connection benefits from this isolation: a buggy request handler cannot corrupt the scheduler.
  2. Threads are better when sharing is the point. A video decoder that splits frames across cores wants to write into a shared output buffer. Threads make that natural; processes would require shared memory and explicit synchronisation.
  3. Threads have lower startup cost. Creating a thread does not require duplicating the address space or copying page tables. For a workload that spawns many small tasks, this matters.
  4. Threads have higher coordination cost. Because they share memory, threads must be carefully synchronised. This is the subject of Unit 6 — and the source of the most difficult bugs in systems programming.

05 Required reading

  • OSTEP — Chapter 26, Concurrency: An Introduction, and Chapter 27, Interlude: Thread API.
  • Operating System Concepts — Chapter 4, Threads and Concurrency.
  • Man pages: pthread_create(3), pthread_join(3), pthread_attr_init(3).

06 Unit discussion

Discussion prompt

Modern programming languages offer very different concurrency primitives. Java and C# have threads with shared memory; Go has goroutines with channels; Erlang has processes with message passing and no shared state.

Choose one of these approaches and argue whether it makes concurrent programming fundamentally easier or merely shifts the difficulty elsewhere. Support your argument with a concrete example of a bug that is easy to write in one model and hard in another.

Post your response to the Unit 4 discussion forum and reply to at least two classmates. Supports CLO2, CLO6, and CLO8.

07 Laboratory connection

Lab 3 — POSIX Threads is completed at the end of this unit. You will write threaded C programs, measure their performance against a single-threaded baseline, and observe what happens when they touch shared state without synchronisation.

Lab 3 coverage
Lesson Lab 3 exercise
4.1 Why Threads?Baseline: single-threaded matrix multiply
4.2–4.3 Thread modelsObserve threads with ps -L and /proc/<pid>/task
4.4 MulticoreMeasure speedup with 1, 2, 4, 8 threads
4.5 POSIX ThreadsImplement and test a threaded work-queue

Prepare for the race

One of the Lab 3 exercises deliberately introduces a data race so you can observe non-determinism. Do not skip it. Seeing a correct program give the wrong answer is the fastest way to appreciate why Unit 6 exists.

08 Knowledge check & quiz

Quick check before you begin

Question 1

What do threads in the same process share, and what does each thread have of its own?

Show answer
Threads in the same process share the code segment, static data, heap, open file descriptors, signal handlers, current working directory, and the page table. Each thread has its own stack, program counter, register set, thread ID, and thread-local storage.

Question 2

What is the fundamental limitation of pure user-level threads that kernel-level threads do not have?

Show answer
Because the kernel does not know about user-level threads, a blocking system call made by any one of them blocks the entire process. The kernel sees only one schedulable entity. On a multicore system, the kernel also cannot run two user-level threads of the same process on two cores simultaneously — again because it sees only one process.

Question 3

Why does adding more threads not always make a program faster?

Show answer
Several reasons. Threads contend for shared resources — locks, caches, memory bandwidth — so adding threads increases overhead. Amdahl's law limits speedup from parallelising only part of a program. Cache coherence traffic grows with the number of cores touching shared data. And threads may be blocked on I/O rather than CPU-bound, in which case adding more does nothing for throughput.
👤