OSOperating Systems · Lesson 3 of 7

Threads & the Scheduler

Your machine runs 400 processes on 8 cores. The illusion that they all run 'at once' is the scheduler switching between them thousands of times per second — the greatest magic trick in computing.

A thread is an execution stream inside a process: its own stack and registers, but sharing the process's memory with sibling threads. Sharing makes communication trivial — and dangerous: two threads writing the same data without coordination is a data race. Processes are isolated and safe; threads are cheap and risky. Every concurrency design chooses a point on that line.

Text
Context switch — how one core runs 'everything at once':

 1. Timer interrupt fires (every few ms)
 2. Kernel saves thread A's registers into memory
 3. Scheduler picks the next thread (priorities, fairness)
 4. Kernel loads thread B's saved registers
 5. Return to user mode — B continues, unaware it ever stopped

Cost: ~1-10 microseconds, plus cold caches afterward.
Thousands of switches per second = smooth multitasking.

Threads block constantly — waiting for disk, network, a lock, or user input. A blocked thread costs no CPU: the scheduler just runs someone else. This is why a web server can hold 10,000 idle connections cheaply, and why 'CPU-bound' (needs cores) versus 'I/O-bound' (needs concurrency while waiting) is the first question of performance tuning.

Python
import threading

counter = 0
lock = threading.Lock()

def work():
    global counter
    for _ in range(100_000):
        with lock:            # without this: lost updates,
            counter += 1      # wrong result, different every run

threads = [threading.Thread(target=work) for _ in range(4)]
for t in threads: t.start()
for t in threads: t.join()
print(counter)                # 400000 — only correct WITH the lock
⚠ Warning
Locks fix races but introduce deadlock: thread A holds lock 1 and wants lock 2; thread B holds 2 and wants 1; both wait forever. Rule that prevents it: every thread acquires locks in the same global order. Concurrency bugs are timing-dependent — they vanish when you look (add a print, race disappears) — which is why the discipline matters more than debugging skill.