Abstractions 1: Threads and Processes
Source: UC Berkeley CS162, Fall 2020/2021 - Prof. John Kubiatowicz, Lecture 3
The previous lecture defined the thread, address space, process, and dual-mode
operation. This one takes the programmer's viewpoint: how you actually create
threads, what they share, and how you create and run processes with the
fork/exec/wait/exit API. It also draws the line between concurrency and
parallelism, which is easy to conflate.
Concurrency is not parallelism
Some definitions worth pinning down: multiprocessing is multiple CPUs (cores); multiprogramming is multiple jobs or processes; multithreading is multiple threads per process. Running two threads concurrently only means the scheduler is free to run them in any order and interleave them - each may run to completion or be time-sliced in big or small chunks.

Why concurrency: overlapping compute and I/O
The practical reason to bother is that operations differ in cost by many orders of magnitude. While one thread waits on a slow operation (disk, network), another can use the CPU. Jeff Dean's "numbers everyone should know" make the gaps vivid.

- L1 cache reference - 0.5 ns; main memory reference - 100 ns (~200x slower).
- Mutex lock/unlock - 25 ns.
- Round trip in the same datacenter - 500,000 ns (0.5 ms).
- Disk seek - 10,000,000 ns (10 ms); packet CA -> Netherlands -> CA - 150,000,000 ns (150 ms).
Creating threads: the pthreads API
At the programmer's level, threads are created through a library. POSIX threads
(pthreads) give you three core calls: pthread_create starts a new thread
running a function, pthread_exit ends the calling thread, and pthread_join
waits for a thread to finish and collects its result.


What threads share, and what they don't
Threads in one process share the process's memory, but each still needs its own execution context. The split is exact: shared state is the heap, global variables, and code; per-thread state is the thread control block, saved registers, and its own stack.

Because every thread has its own stack but they all live in one address space, the address space has to hold several stacks at once.

Everything in the shared column is reachable by every thread at the same time. If two threads write the same global or heap object without coordination, the result depends on the interleaving. This is the setup for the entire Synchronization unit - threads share memory by design, so sharing safely is a problem you must solve, not one you get for free.
System calls: crossing into the kernel
A user program cannot touch hardware directly; it asks the kernel through a system call. A portable OS library exposes a uniform interface, and the same call works across very different hardware underneath.

Processes: the heavier unit
Threads share everything; sometimes you want the opposite - full isolation. That is a process. Everything outside the kernel runs inside some process, including the shell, and processes are created and managed by other processes.

The OS exposes a small process-management API.

fork: cloning a process
fork() creates a near-exact copy of the calling process. The child gets its own
copy of the address space. The trick is the return value: fork returns twice -
once in the parent (returning the child's PID) and once in the child (returning 0).

fork returns twice, and you can't assume who runs firstAfter fork, both processes continue from the same line with different return
values. Which one the scheduler runs first is not defined. Code that assumes the
parent (or child) runs first is already broken.

exec: running a different program
fork alone only ever runs the same program. To run a different one, the
child calls exec, which replaces its address space with a new program image
while keeping the same process (and PID). The fork-then-exec pair is how a shell
launches a command.

forkduplicates the current process; both parent and child return from the same call with different values.execreplaces the current program image with a new one, keeping the same process.- A shell runs a command by
forkthenexecin the child, while the parentwaits - separating "make a new process" from "choose what it runs".
Bootstrapping
If every process is created by another process, where does the first one come
from? The kernel starts it directly at boot - the init process - and every
other process on the system descends from it.

Recap
- Concurrency is not parallelism: concurrency is interleaving on one core; parallelism needs multiple cores. Correct code must survive every interleaving.
- Threads are created with pthreads (
pthread_create/exit/join) and share the heap, globals and code while keeping their own stack and registers. - A system call is the controlled crossing from user code into the kernel.
- A process is the isolated, heavier unit, managed with
fork/exec/wait/exit. forkcopies a process (returning twice);execreplaces its program; together they start every new program. The kernel bootstraps the first process (init).- Next: the abstraction those processes touch constantly - files and I/O.