A thread is an abstraction that allows a program to execute multiple independent streams of control concurrently on a single physical processor. Threads can be thought of as lightweight processes that share the same address space and resources.
Threads enable programs to handle asynchronous operations, such as input and output, without blocking the entire program. Instead of waiting for an operation to complete, a thread can be paused and resumed, allowing other threads to run in the meantime.
A thread can be in one of several states:
Transitions between states are managed by the operating system or thread library.
Threads are created by a function which typically takes a procedure and arguments. This "forks" the control flow, starting a new thread that runs concurrently.
Joining a thread blocks the current thread until the joined thread completes, optionally retrieving its return value. This provides synchronous behavior.
Yielding temporarily pauses the current thread, allowing others to run. Notably the thread remains runnable.
Thread switching occurs at the instruction level:
The program counter is handled implicitly via the stack, as the return address is saved/restored.
Scheduling policies determine which runnable thread is selected to run next on the CPU when multiple threads are ready. They balance competing goals such as fairness (equal CPU time), responsiveness (quick reaction to events), priority (important tasks first), and efficiency (maximizing throughput). Policies can be preemptive (interrupting running threads) or non-preemptive (threads run until they yield or block).
In round-robin scheduling, threads take turns running for a fixed time slice (typically 10-100ms). When a thread's time expires, it is preempted and placed back in the runnable queue, allowing the next thread to run. This ensures fair sharing of CPU time among threads of equal priority, preventing any single thread from monopolizing the processor.
Round robin may not prioritize urgent tasks and can introduce overhead from frequent context switches.
Priority-based scheduling assigns each thread a priority level, with higher-priority threads running before lower ones. Within the same priority, round-robin is often used. This is useful for systems where certain tasks (e.g., user interface updates or real-time processing) must respond quickly.
Priorities can be static (fixed) or dynamic (adjusted based on behavior). A drawback is starvation, where low-priority threads may never run if high-priority ones dominate.
Preemption allows the scheduler to interrupt a running thread to switch to another, improving responsiveness. It occurs in two main ways: priority preemption (when a higher-priority thread becomes runnable) and time-based preemption (at the end of a time slice). This is implemented using timer interrupts that periodically check if a switch is needed.
Preemptive scheduling is essential for interactive and real-time systems but adds complexity, as threads must handle being interrupted at any point.