Concurrency and Thread Dispatching
Processes (Review)
- Memory I/O State, CPU state, sequential stream of instructions.
- Each process has its own memory space and I/O state.
- Process switch overhead is high due to memory/IO state.
- Process creation is high. Provides CPU and Memory/IO protection.
- Sharing overhead is high.
Multithreaded Processes (Review)
- Share the same memory/IO state.
- Switch overhead is low (only CPU state).
- Thread creation is low.
- No Memory/IO protection.
- Low sharing overhead.
Why Processes & Threads? (Review)
- Processes: Unit of execution and allocation; Virtual Machine abstraction.
- Threads: Decouple allocation and execution; Run multiple threads within the same process.
Thread State (Review)
- Shared State: Memory, I/O.
- Private State (TCB): CPU registers, program counter PC, Execution stack.
Execution Stack Example (Review)
- Stack holds function arguments and return addresses, enabling recursion.
Single-Threaded Example (Review)
- A program may not complete all tasks if a function never finishes.
Use of Threads (Review)
- Multiple threads can run concurrently, behaving as if there are multiple CPUs.
Cooperating Threads
- Allow resource sharing, speedup via overlapping I/O and computation, and modularity.
Multithreaded Processes
- PCB points to multiple TCBs.
- Switching threads within a block is a simple thread switch.
- Switching threads across blocks requires changes to memory and I/O address tables.
Lifecycle of a Thread
- States: new, ready, running, waiting, terminated.
- TCBs are organized in queues based on state.
Ready Queues
- Most threads are in the ready state.
- TCBs are in scheduler queues when not running.
Ready Queue And Various I/O Device Queues
- Separate queue for each device/signal/condition, with different scheduler policies.
Choosing a Thread to Run
- Dispatcher chooses the next thread to run using scheduling priorities (LIFO, FIFO, Priority queue).
Per Thread State
- TCB stores execution state, scheduling info, pointers, and a pointer to the PCB.
Dispatch Loop
- The OS dispatching loop runs threads, chooses the next thread, saves and loads CPU state.
Running a Thread
- Load thread state into CPU, load environment, and jump to PC.
- Dispatcher regains control through internal (I/O, yield) or external (preemption) events.
Yielding through Internal Events
- Threads yield CPU when blocking on I/O, waiting for a signal, or calling yield().
Stack for Yielding a Thread
- Dispatcher switches to a new thread by saving the current thread's state and loading the new thread's state.
Need for External Events
- External events (interrupts, timer) ensure dispatcher can regain control.
Detour: Interrupt Controller
- Interrupt controller manages interrupt requests and priorities.
Preemptive Multithreading
- Timer interrupts force scheduling decisions, preempting threads.
ThreadFork(): Create a New Thread
- Creates a new thread and adds it to the ready queue by allocating stack/TCB and initializing TCB.
How do we initialize TCB and Stack?
- Initialize register fields (stack pointer, PC return address, argument registers).
How does Thread get started?
- runnewthread() selects the TCB and returns into ThreadRoot().
What does ThreadRoot() look like?
- ThreadRoot() calls thread code and then ThreadFinish().
What does ThreadFinish() do?
- Enters kernel mode, wakes up waiting threads, marks thread for destruction, and calls runnewthread().
ThreadJoin() system call
- Allows one thread to wait for another to finish.
Use of Join for Traditional Procedure Call
- ThreadFork() followed by ThreadJoin() is logically equivalent to a traditional procedure call.
Summary
- Thread state is in the TCB (registers, PC, stack pointer) with states including New, Ready, Running, Waiting, or Terminated.
- Interrupts return control to OS, enabling preemptive multithreading.
- ThreadFork() creates threads, ThreadRoot() executes code, and ThreadFinish() prepares for destruction.
- Threads use ThreadJoin() to wait for others.