Quiz 2

Threads

1049 words
5 min read
Python Week 1: the first filter for runtime behavior
Visual companion
Python
Type and operator map

Python Week 1: the first filter for runtime behavior

View
Revision summary

What this note is really saying

Short form

# Threads ## 🎯 Learning Objectives - Differentiate between a process and a thread - Compare user-level vs kernel-level threads - Explain the three threading models (many-to-one, one-to-one, many-to-many) - Write multithreaded programs using the pthreads API - Understand thread pools and their benefits * * * ## 1. T...

Threads

🎯 Learning Objectives

  • Differentiate between a process and a thread
  • Compare user-level vs kernel-level threads
  • Explain the three threading models (many-to-one, one-to-one, many-to-many)
  • Write multithreaded programs using the pthreads API
  • Understand thread pools and their benefits

1. Thread Concepts

1.1 Intuition

A thread is a lightweight process — the smallest unit of CPU utilization. A process can have multiple threads that share the same address space (code, data, heap) but each has its own stack and register set. Think of a process as a house with multiple rooms (threads) — they share the kitchen and living room (heap/data) but each has their own bedroom (stack).

1.2 Process vs Thread

(Diagram)
AspectProcessThread
Address spacePrivate (separate per process)Shared with other threads in the same process
Creationfork() — expensive (copy address space)pthread_create() — cheap (share address space)
Context switchSlow (page table switch, TLB flush)Fast (same address space)
CommunicationIPC (pipes, sockets, shared memory)Direct (shared global variables)
IndependenceFully independent (one crash doesn't affect others)Dependent (one thread crash can crash all)

2. Threading Models

(Diagram)
ModelDescriptionProsConsExamples
Many-to-OneMany user threads → 1 kernel threadEfficient context switch (user space)Blocking one blocks allGreen threads (Solaris)
One-to-One1 user thread → 1 kernel threadTrue parallelism (multicore)Overhead (creating kernel threads)Linux (NPTL), Windows, macOS
Many-to-ManyM user threads → N kernel threadsBest of both (flexibility + parallelism)Complex implementationSolaris (before v9)

2.1 User-Level Threads (ULT)

  • Managed entirely in user space (no kernel involvement)
  • Thread library handles scheduling (e.g., GNU Pth)
  • Pros: Fast creation/context switch, no kernel modification needed
  • Cons: Blocking one thread blocks all (kernel sees one process), no true parallelism

2.2 Kernel-Level Threads (KLT)

  • Managed by the kernel (OS scheduler)
  • Pros: True parallelism (scheduled independently on different cores), one thread blocks independently
  • Cons: Slower creation/context switch (system calls), more overhead

3. pthreads API

POSIX threads (pthreads) is the standard threading API on Unix-like systems.

3.1 Basic pthread Functions

c
#include <pthread.h>
// Create a thread
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);
// Wait for a thread to finish
int pthread_join(pthread_t thread, void **retval);
// Exit current thread
void pthread_exit(void *retval);
// Get own thread ID
pthread_t pthread_self(void);
// Compare thread IDs
int pthread_equal(pthread_t t1, pthread_t t2);

3.2 Worked Example: Multithreaded Sum

c
#include <stdio.h>
#include <pthread.h>
#define NUM_THREADS 4
#define ARRAY_SIZE 1000
int arr[ARRAY_SIZE];
int partial_sums[NUM_THREADS] = {0};
void* sum_partial(void* arg) {
    int thread_id = *(int*)arg;
    int start = thread_id * (ARRAY_SIZE / NUM_THREADS);
    int end = start + (ARRAY_SIZE / NUM_THREADS);
    for (int i = start; i < end; i++) {
        partial_sums[thread_id] += arr[i];
    }
    printf("Thread %d: partial sum = %d\n", thread_id, partial_sums[thread_id]);
    pthread_exit(NULL);
}
int main() {
    pthread_t threads[NUM_THREADS];
    int thread_ids[NUM_THREADS];
    int total_sum = 0;
    // Initialize array
    for (int i = 0; i < ARRAY_SIZE; i++) arr[i] = i + 1;
    // Create threads
    for (int i = 0; i < NUM_THREADS; i++) {
        thread_ids[i] = i;
        pthread_create(&threads[i], NULL, sum_partial, &thread_ids[i]);
    }
    // Wait for all threads
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
        total_sum += partial_sums[i];
    }
    printf("Total sum = %d (expected %d)\n", total_sum, 1000*1001/2);
    return 0;
}
Output:
pseudo
Thread 0: partial sum = 31375
Thread 1: partial sum = 93875
Thread 2: partial sum = 156375
Thread 3: partial sum = 218875
Total sum = 500500 (expected 500500)

4. Thread Pools

A thread pool creates a fixed number of threads at startup and reuses them for multiple tasks. (Diagram) Benefits:
  • Avoids thread creation overhead (amortized across tasks)
  • Controls resource usage (max threads)
  • Natural load balancing

5. Common Pitfalls

Pitfall 1: Data races (unsynchronized shared data)

c
int counter = 0;
void* increment(void* arg) {
    for (int i = 0; i < 100000; i++) counter++;  // Race condition!
}
Fix: Use mutex locks or atomic operations.

Pitfall 2: Deadlock with multiple mutexes

Mistake: Thread A locks L1 then L2, Thread B locks L2 then L1. Fix: Establish a fixed lock ordering (always lock in the same sequence).

Pitfall 3: Memory leaks from detached threads

Mistake: Creating detached threads and losing the handle → can't join, resources leaked. Fix: Keep track of all threads; either join or properly detach.

6. 📐 Key Formulas / Concepts

ConceptDescriptionKey Insight
ThreadLightweight process unitShares address space, private stack
pthread_createCreate a threadTakes function pointer and argument
pthread_joinWait for threadCollects return value
Thread poolReusable thread setAmortizes creation overhead
Data raceUnsynchronized concurrent accessUse mutexes to protect shared data

7. 📝 Practice Questions

Q1: How many threads does fork() create? How many does pthread_create() create?
Answer: fork() creates one new process (which starts with a single thread). pthread_create() creates one new thread within the existing process (sharing address space). Q2: In the one-to-one threading model, what happens when a thread makes a blocking I/O call?
Answer: The kernel blocks only that specific kernel thread. Other threads (both user and kernel) continue running independently. This is a key advantage over many-to-one, where the entire process would block. Q3: Why is context switching between threads of the same process faster than between processes?
Answer: Threads share the same address space, so there's no need to switch page tables or flush the TLB. Only registers and stack pointers need to be saved/restored. Q4: What is the purpose of pthread_join()?
Answer: It causes the calling thread to block until the target thread terminates. It also collects the return value (if any) and cleans up the thread's resources. Q5: Explain the difference between user-level and kernel-level threads.
Answer: User-level threads are managed by a thread library in user space without kernel involvement — fast creation but no true parallelism. Kernel-level threads are managed by the OS kernel — true parallelism but higher overhead. User-level threads cannot take advantage of multiple cores.

8. 🔗 Cross-References

Document outline

Keep your place and jump directly to a heading.

Table of Contents
System Normal // Awaiting Context

Intelligence Hub

Navigate the knowledge graph to generate context. The Hub adapts dynamically to surface backlinks, related notes, and metadata insights.