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.
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.
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.