More about Threads

Subpage of Operating Systems

Diving deep into the design and structure of Operating Systems.

Processes are expensive to create and switch between (they require switching memory maps, flushing TLBs, etc.). Threads are “lightweight” – creating a new thread is much faster because it reuses the existing process environment. This makes threads ideal for:

  • Servers handling many clients (e.g., web server with a thread per request).
  • Parallel computation on multi-core systems (shared memory simplifies data sharing compared to separate processes).

Thus, at the core of every multitasking OS is a scheduler loop (some would even call this loop the OS):

while (1) {
    thread = choose_next_thread();   // scheduling policy
    switch_to(thread);               // context switch
    // The CPU now runs the thread until it yields or is interrupted
}

Here, switch_to() performs a form of Context Switching. This is cheaper than usual context swithching because threads of the same process share memory mapping.

Initialising Threads

This is done following the threadroot Subroutine. The program counter (PC) is set to point to the thread’s entry function (often called threadroot). The thread is then placed on the ready queue, waiting for the scheduler to dispatch it.

When the scheduler switches to this thread, it begins execution from the threadroot, using its initialized stack, just like any other process.

Kernel Threads

Why have kernel threads? Because only the kernel can:

  • Handle privileged operations (I/O, memory mapping, interrupt handling).
  • Perform the actual context switch between threads (saving/restoring registers).
  • Manage scheduling decisions that affect all threads system-wide.

In the one-to-one model (e.g., Linux’s NPTL, Windows threads), each user thread is backed by a kernel thread.

When a user thread makes a system call (e.g., read()), the transition from user mode to kernel mode occurs. The associated kernel thread is the one that executes the kernel part of the operation. The “switch operation that handles the saving and loading of the information” is the context switch between kernel threads (or between a kernel thread and a user thread depending on design).

Switching between threads of the same process is faster than switching between processes because:

  • No change of address space (no TLB flush, no page table switch).
  • Fewer register saves/restores (no need to save segment registers, FPU state can be lazy, etc.).

Your notes then describe an optimisation: one-to-many relationship (user-level threads, or “green threads”). A single kernel thread manages multiple user threads entirely in user space. The user-level thread library performs its own context switches (using setjmp/longjmp or assembly) without involving the kernel.

Advantage: Even faster switching – no system calls at all for scheduling. You can have thousands of user threads with minimal overhead.

Disadvantage: If a user thread makes a blocking system call (e.g., read()), the entire kernel thread (and all its associated user threads) blocks, because the kernel only sees one thread. This defeats the purpose of concurrency. Solutions include:

  • Wrapping blocking calls with non-blocking variants (e.g., select/poll/epoll).
  • Using a many-to-many model (kernel-managed “virtual processors”) – complex and rarely used today.

Modern practice: One-to-one is standard (Linux, Windows, macOS). The overhead is acceptable given faster context switches and multi-core support.

Simultaneous multithreading (SMT) 

Intel’s implementation is called Hyper-Threading Technology. A single physical core has duplicated architectural state (e.g., two sets of registers, two program counters) but shares most execution resources (ALUs, caches, etc.). The core can interleave instructions from two threads on the same cycle, filling pipeline slots that would otherwise be idle (e.g., when one thread stalls waiting for a cache miss).

From https://medium.com/@ITsolutions/will-hyper-threading-improve-processing-performance-15cba11add74
From https://medium.com/@ITsolutions/will-hyper-threading-improve-processing-performance-15cba11add74

Without SMT, a single-threaded core might achieve only 60–80% pipeline utilisation due to hazards and memory latencies. SMT can push it toward 90–100% by keeping the pipeline fed with work from another thread.

Key implication for scheduling: The OS sees each hardware thread (logical core) as a separate CPU. The scheduler can assign threads to logical cores. The hardware then interleaves execution at the microarchitectural level. This is invisible to the programmer except for performance effects (e.g., cache contention between threads on the same physical core).

/ Continue

Follow the technical trail.

Use the dense notes as the source material, then move through the guided route, writing, or project proof when you want a cleaner entry point.