Lesson 3 of 7

Threads & the Scheduler

A machine with 8 cores can run hundreds of processes because the scheduler switches between them thousands of times per second. You'll learn how threads and scheduling work, and how to avoid data races.

A thread is an execution stream inside a : its own and registers, but sharing the process's 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 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.

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
Python (loads on first run)Ctrl+Enter runs
Finished reading?Mark it done to track your progress.

Tip: use the ← and → keys to move between lessons.