The Big Picture: A Few Counters, A Long Queue

Picture a bank with 4 counters (the CPU cores) and 200 customers (the threads) who all want service. A manager decides who goes to which counter, and for how long. If one customer takes too long, the manager politely sends them back to the queue so others get a turn.

That manager is the scheduler: a part of the Windows kernel. It runs thousands of times a second.

   READY QUEUE (threads waiting for a turn)           CORES
   ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐                   ┌────────────┐
   │ T7 │ │ T3 │ │ T9 │ │ T2 │ │ T5 │ ── scheduler ───► │ core 0  T1 │
   └────┘ └────┘ └────┘ └────┘ └────┘                   │ core 1  T4 │
                                                        │ core 2  T8 │
                                                        │ core 3  T6 │
                                                        └────────────┘

3.1 Scheduler Responsibilities

What it is: The scheduler (also called the dispatcher in Windows) decides which thread runs on which core, and for how long.

Its jobs:

JobPlain meaning
Pick the next threadWhen a core becomes free, choose the best waiting thread
Be fairDo not let one thread hog a core forever
Respect priorityMore important threads go first (Section 5)
Use all cores wellSpread work so no core sits idle while threads wait
Handle waitingMove threads out of the way when they wait for disk, network or a lock
Switch cleanlySave one thread's state and load another's (Section 6)

Windows rule in one line: At any moment, the highest-priority ready thread should be the one running. Among threads with the same priority, they take turns in order (called round-robin).

When does the scheduler run? (It does not poll constantly. It is woken by events.)

  1. A thread's time slice ran out.
  2. A thread starts waiting (for I/O, a lock, a timer).
  3. A thread stops waiting and becomes ready, perhaps with higher priority than the running one.
  4. A thread's priority changes.
  5. A thread ends.

The first event comes from a hardware timer interrupt (Chapter 4, interrupts). The timer pokes the CPU at a regular beat (about every 15.6 ms by default on most PCs, shorter if a program asks for it). The kernel then checks whether the running thread has used up its slice.

3.2 Time Slices (Quantum)

What it is: A time slice, called a quantum in Windows, is the amount of time a thread may run before the scheduler may swap it out for another thread of the same priority.

Think of a game of "pass the controller" where everyone gets 5 minutes before the next person.

How long is it? It is a few clock ticks (a tick is one timer beat). On Windows client versions (Windows 10 and 11) the quantum is typically about 2 ticks, so around 20 to 30 milliseconds, and the foreground window's thread can get a longer one. On Windows Server it is longer, since servers favour throughput over feel. The exact numbers can differ by version and settings, so treat these as "about".

Quantum is counted in ticks, not in exact CPU cycles. Windows keeps a counter for each running thread. Every tick, the counter goes down. When it reaches zero, the quantum has ended.

Core 0, two threads of equal priority, quantum = Q

 time ──►
 Thread A: ██████ Q ██████              ██████ Q ██████
 Thread B:                ██████ Q ██████              ██████
                         ▲              ▲
                    A's quantum    B's quantum
                    ends, swap     ends, swap

Why not make it tiny or huge?

QuantumEffect
Too shortMany switches. Each switch costs time (Section 6), so more time is wasted
Too longA thread hogs the core. The mouse and screen feel laggy

A thread that waits before its quantum ends (say, to read a file) just gives up the rest. It does not have to "finish" its slice.

Quantum can be cut short. If a higher-priority thread becomes ready, the scheduler takes the core at once, even in the middle of a slice. That is preemption (next topic).

3.3 Preemptive Multitasking

What it is: Preemptive means the OS can forcibly take the CPU away from a running thread, without asking.

There are two styles of multitasking:

StyleWho decides when to switchProblem
CooperativeThe thread itself ("I'm done, you go next")One badly behaved program that never yields freezes the whole PC. Old Windows 3.1 worked like this.
PreemptiveThe OS (a timer interrupt forces the switch)Safe: no program can hog the CPU. Used by Windows NT and every Windows since (including Windows 10 and 11).

How the OS takes the CPU back:

 1. Thread A is running on core 0.
 2. Timer interrupt fires ──► CPU jumps into the kernel (Chapter 4).
 3. Kernel sees: A's quantum is used up (or a more important thread is ready).
 4. Kernel saves A's registers, picks thread B, loads B's registers.
 5. CPU "returns" from the interrupt, now running B.
 A never knew it was paused.

A thread is also preempted when a higher-priority thread wakes up. If you press a key, the thread handling the keyboard may instantly preempt a background number-cruncher.

Cannot be preempted: code running at a high IRQL (interrupt request level, an internal Windows ranking). Interrupt handlers and some kernel code run in a way that threads cannot interrupt them. Ordinary program threads are always preemptible.

3.4 Multi-Core Scheduling

What it is: With many cores, Windows keeps a ready queue and chooses a core for each thread. Each logical processor can run one thread at a time.

Windows tries to do two things at once:

  1. Keep every core busy (do not let a core idle while threads wait).
  2. Keep a thread on the same core it used last (this is called soft affinity or the thread's ideal processor).

Why stay on the same core? That core's cache (Chapter 2) already holds the thread's recent data. Moving the thread means starting with a cold cache and slower memory reads. This is cache warmth.

  Thread X ran on core 2 ──► its data is in core 2's L1/L2 cache (warm)
  Next turn on core 2     ──► fast, data still there
  Next turn on core 6     ──► slow at first, data must be fetched again

Other things the scheduler knows about:

Hardware factWhat Windows does
Hyper-threading (SMT)Two logical processors share one physical core. If a whole physical core is free, it prefers to put a new thread there rather than doubling up on a busy core
P-cores and E-cores (Intel 12th gen and later)Windows 11 uses a hint from the CPU called Thread Director to place important, heavy threads on fast P-cores and background threads on efficient E-cores
NUMA (several memory nodes, on big servers)Tries to run a thread near the memory it uses
Processor groupsWindows divides more than 64 logical processors into groups of up to 64
Power savingMay "park" idle cores to save energy and wake them when load rises

The Intel Core i9 in Figure 10.1 (Section 1) is a good example: 8 P-cores with hyper-threading and 16 E-cores. Windows 11 decides, thread by thread, which kind of core is the best fit.

Seeing it: In Task Manager → Performance → CPU, right-click the graph and choose Change graph to → Logical processors. You will see one small graph for each logical processor, and watch load move between them.

3.5 CPU Affinity

What it is: Affinity is a rule that says which cores a thread or process is allowed to run on. It is stored as a bitmask: one bit per logical processor (1 = allowed, 0 = not allowed).

 8 logical processors:  CPU7 CPU6 CPU5 CPU4 CPU3 CPU2 CPU1 CPU0
 Affinity mask:           0    0    0    0    1    1    1    1   = 0x0F
 Meaning: may run only on CPUs 0, 1, 2 and 3
LevelMeaningHow
Process affinityAll threads of the process obey this maskTask Manager → Details → right-click the process → Set affinity
Thread affinityA single thread's maskSetThreadAffinityMask (programs only)
Ideal processorA hint: "prefer this core"SetThreadIdealProcessor

Hard vs soft:

  • Soft affinity = the scheduler's own preference (ideal processor). The thread can still go elsewhere if needed.
  • Hard affinity = the mask. The thread can run only there. If those cores are busy, the thread waits, even while other cores are idle.

Why would you set affinity?

ReasonExample
Keep an old, badly made program calmAn old game that glitches on many cores
Reserve coresKeep a heavy background job off the cores your main app uses
Cache or NUMA tuningSpecialist servers and real-time systems

Warning: For normal use, do not touch affinity. The scheduler knows more about the current load than you do. A bad mask can make a program slower. The setting is lost when the process ends (unless the program sets it itself).

PowerShell example (works on a process that is running; replace the name):

$p = Get-Process notepad
$p.ProcessorAffinity            # shows the mask, e.g. 255 for 8 logical CPUs
$p.ProcessorAffinity = 3        # binary 0011 = CPUs 0 and 1 only

Summary

TermPlain meaning
Scheduler / dispatcherThe kernel part that picks which thread runs on which core
Core ruleHighest-priority ready thread runs; equals take turns
Time slice (quantum)How long a thread may run before an equal-priority thread gets a turn (about 20–30 ms on client Windows)
PreemptiveThe OS can take the CPU away at any time
CooperativeThreads give up the CPU themselves (old, unsafe)
Ready queueThreads that are ready and waiting for a core
Soft affinity / ideal processorA preferred core; a hint only
Hard affinityA mask of cores the thread must stay on
Thread DirectorIntel hint that helps Windows 11 pick P-core or E-core