Welcome to the second lesson in our module on Operating Systems. In the previous lesson, we established a clear understanding of a process as a heavyweight unit of execution managed by the OS. We saw how the OS juggles these processes using context switching, giving us the power of multiprogramming. Now, we're going to zoom in and explore a more granular form of concurrency: threads.
This lesson will focus on what happens inside a process. We will dissect the concept of a thread as a "lightweight process" and critically compare the two main implementations: user-level threads and kernel-level threads. Understanding their respective advantages and disadvantages is crucial for GATE, as questions often test these trade-offs. We will then analyze the three primary multithreading models (Many-to-One, One-to-One, and Many-to-Many) that define the relationship between the threads your application creates and the threads the OS manages. This knowledge is fundamental for grasping modern concurrent programming and system performance.
From Processes to Threads: The Need for Lighter Concurrency
A process, with its own address space, open files, and accounting information, is a powerful but resource-intensive abstraction. Creating a new process and switching between existing ones involves significant overhead, as you saw with context switching and the PCB.
What if an application needs to perform several tasks concurrently that share the same data and resources? For example, a web server handling multiple client requests, or a word processor formatting text in the background while you type. Creating a separate process for each task would be inefficient.
This is where threads come in. A thread is a basic unit of CPU utilization. It comprises a thread ID, a program counter (PC), a register set, and a stack. Crucially, it shares with other threads belonging to the same process its code section, data section, and other operating-system resources, such as open files and signals.
A process with a single thread versus a process with multiple threads. Note that the threads share Code, Data, and Files, but each has its own Registers and Stack. This shared context is what makes them "lightweight".
Because threads share the memory space of their parent process, creating a thread and switching between threads of the same process is much faster than doing so for processes.
User-Level vs. Kernel-Level Threads
Threads can be implemented in two main ways, and the distinction between them is a source of many GATE questions. The difference lies in who is responsible for managing the threads: a user-level library or the OS kernel itself.
L-1.12: User Level Vs Kernel Level Thread in Operating System | All Imp Points
To build a solid foundation, let's watch a video from Gate Smashers that provides a direct, point-by-point comparison of user-level and kernel-level threads. This is exactly the kind of analysis you need for the exam.
Please watch the following segments carefully: Management: First, understand who manages each type of thread. Watch the explanation from this section. Context Switching: Next, focus on the performance difference in switching between threads. Watch the comparison of context switching speeds. Blocking Operations: This is the most critical difference. Pay close attention to what happens when a thread performs a blocking I/O operation in this crucial segment. Shared Resources: Finally, reinforce your understanding of what threads share by watching the clarification from this part.
Let's consolidate what you've just learned into a clear comparison, which is a high-yield format for your notes.
User-Level Threads (ULTs)
- Management: Managed by a thread library in user space (e.g., POSIX Pthreads, Java threads). The kernel is completely unaware of the existence of these threads; it only sees the single process that contains them.
- Creation & Switching: Creation, scheduling, and context switching are extremely fast as they are simply function calls within the user-level library. No system calls or mode switches to the kernel are required.
- Portability: Highly portable, as the thread library can be implemented on any OS.
- Major Disadvantage (Blocking): If one user-level thread executes a blocking system call (e.g.,
read()from a slow disk), the kernel blocks the entire process. This freezes all other threads within that process, even if they were ready to run. - Major Disadvantage (Multiprocessing): Since the kernel sees only one thread of execution (the process itself), ULTs cannot run in parallel on a multi-core system. The kernel will only schedule the process on a single CPU core at any given time.
Kernel-Level Threads (KLTs)
- Management: Managed directly by the operating system. The kernel is aware of and schedules each thread individually. KLTs are sometimes called "kernel threads" or "lightweight processes" by the OS.
- Creation & Switching: Creation and context switching are slower than for ULTs because they involve system calls and OS intervention. A context switch between KLTs of the same process is still faster than a full process context switch, but the overhead is non-trivial.
- Advantage (Blocking): If one kernel-level thread blocks, the kernel can simply schedule another thread (from the same or a different process) to run. The process as a whole does not get blocked.
- Advantage (Multiprocessing): The kernel can schedule multiple threads from the same process to run simultaneously on different CPU cores, enabling true parallelism.
Here's a summary table for quick revision:
| Feature | User-Level Threads (ULTs) | Kernel-Level Threads (KLTs) |
|---|---|---|
| Management | User-level thread library | Operating System (Kernel) |
| Kernel Awareness | Kernel is unaware | Kernel is aware |
| Creation/Switching Speed | Very Fast (procedure calls) | Slower (requires system calls/mode switches) |
| Blocking System Call | Blocks the entire process | Blocks only the specific thread |
| Multiprocessing | Cannot utilize multiple cores | Can run in parallel on multiple cores |
| Example Implementations | Early Java (Green threads), pthread in many-to-one mode |
Linux, Windows, modern macOS |
Multithreading Models
So, we have threads created by programmers (user threads) and threads scheduled by the OS (kernel threads). How do they relate to each other? This relationship is defined by multithreading models. There are three common models that map user threads to kernel threads.
Multithreading Models & Hyperthreading
This Neso Academy video provides excellent diagrams and explanations for the three multithreading models. It logically builds from the simplest model to the most flexible one.
Please watch the video to understand how these models work: Introduction: First, get an overview of the two thread types and the need for a relationship between them from the introduction. Many-to-One Model: Understand the first model where many ULTs map to a single KLT. Focus on its efficiency and its critical flaws in this segment. One-to-One Model: Next, see how this model solves the problems of the first one by providing a dedicated KLT for each ULT. Watch from this explanation. Many-to-Many Model: Finally, learn about the hybrid model that combines the benefits of the previous two. Watch the concluding segment from this part.
Let's review these three models based on the video.
1. The Many-to-One Model
In this model, many user-level threads are mapped to a single kernel thread. Thread management is handled efficiently in user space.
- Pros: Very efficient thread management since it's all done by the user-level library.
- Cons:
- It suffers from the exact same problems as pure ULTs: a single blocking system call from any thread will block the entire process.
- True parallelism is impossible, as only one thread can access the kernel at a time. The process can only run on one CPU core.
This model is largely obsolete today but is important to understand for context.
2. The One-to-One Model
This model maps each user-level thread to a corresponding kernel thread.
- Pros:
- It overcomes the major flaws of the many-to-one model. A blocking call by one thread does not block the others.
- It provides true parallelism on multi-core systems.
- Cons:
- Creating a user thread requires creating a corresponding kernel thread, which adds overhead and can burden system performance.
- An operating system typically has a limit on the number of kernel threads it can support, which might restrict the number of threads an application can create.
This is the most common model used in modern operating systems like Linux, Windows, and macOS.
3. The Many-to-Many Model
This model multiplexes a number of user-level threads onto a smaller or equal number of kernel threads. The number of kernel threads can be specific to an application or a machine.
- Pros:
- It's a compromise that combines the best of both worlds. Developers can create as many user threads as they need without the overhead of the one-to-one model.
- A blocking system call does not block the entire process; the kernel can schedule another available thread.
- It allows for parallelism on multi-core systems.
- Cons: Significantly more complex to implement in both the user-level library and the kernel.
A variant of this model, known as the two-level model, allows a user thread to be bound to a specific kernel thread, combining Many-to-Many with One-to-One.
Conclusion
In this lesson, we moved from the "heavyweight" process to the "lightweight" thread, a critical concept for understanding concurrency and performance in modern systems. You are now prepared to tackle GATE questions that test the nuanced differences between thread implementations and models.
Here are the key takeaways from our session:
- A thread is a basic unit of execution within a process, sharing the process's memory and resources but having its own stack, registers, and program counter.
- User-Level Threads (ULTs) are fast to manage but suffer from major drawbacks: a blocking call freezes the entire process, and they cannot exploit multi-core architectures.
- Kernel-Level Threads (KLTs) are managed by the OS, enabling true parallelism and robust handling of blocking calls, but at the cost of higher overhead for creation and switching.
- Multithreading models define the mapping between ULTs and KLTs:
- Many-to-One: Efficient but flawed (blocking, no parallelism).
- One-to-One: Robust and parallel but can be resource-intensive. This is the dominant model today.
- Many-to-Many: The most flexible and theoretically optimal model, but also the most complex.
In our previous lesson, we saw how to create and manage separate processes. In this lesson, we explored how to run concurrent tasks within a single process. A natural next question arises: how do these separate processes and threads communicate and coordinate with each other? Our next lesson will address exactly that, as we dive into the world of Inter-Process Communication (IPC), covering mechanisms like pipes, message queues, and shared memory.