Processes and threads
A process is an executing program with resources and an address-space context. Threads within a process share memory but have separate execution state and stacks. Separate processes usually isolate ordinary memory; communication needs pipes, sockets or explicitly shared facilities. A process may be ready, running or blocked waiting for I/O. A program file is passive; multiple processes can execute that same file. Thread concurrency does not imply simultaneous CPU execution, and interpreter-specific constraints should not be confused with operating-system guarantees.
Do two threads in one process share all stack frames?
Worked solution
No; their execution stacks are separate, while shared objects may be accessed by both.
Scheduling and context switching
A scheduler chooses ready work. First-come-first-served is simple but a long job delays short jobs. Round robin rotates a time quantum among ready tasks, improving interactive response while increasing switching overhead if the quantum is tiny. Turnaround measures completion minus arrival; response measures first service minus arrival. Saving and restoring registers allows execution to resume after a context switch. Fairness, throughput and latency are different objectives; a policy cannot be judged without its workload and goal.
Is response time identical to turnaround time?
Worked solution
No; a job may start quickly but finish much later.
Virtual memory and paging
Programs use virtual addresses translated to physical storage through page mappings. A page number chooses a mapping; an offset chooses a byte within that page. With 4096-byte pages, virtual address 8197 has page 2 and offset 5. Protection and separate mappings isolate processes; a TLB caches translations. A page fault transfers control to the operating system and may allocate, load or reject access. It does not always mean a disk read. Virtual memory is an address-space abstraction, not simply extra RAM from a disk.
For 1024-byte pages, split virtual address 2051.
Worked solution
Page 2, offset 3.
Files, handles and persistence
A file provides persistent named data; a handle tracks an open instance and its position. Close resources reliably with a context manager even after an exception. A directory organises names, while permissions govern allowed operations. Buffering means a successful write call need not imply durable storage on physical media. Applications needing crash-safe updates require explicit durability and atomicity policies. Our temporary-directory lab cleans its own files and demonstrates lifetime, not durable database transactions.
Does writing to a buffer guarantee survival after power loss?
Worked solution
No; durability requires additional guarantees.
Race conditions, locks and deadlock
A read-modify-write operation can interleave: both threads read zero, both write one, and one increment is lost. Protect the whole critical section with a shared lock, not just its final write. Python's GIL in conventional builds is not an application-level transaction guarantee. Deadlock can arise when threads hold one resource while waiting for another in a cycle. A consistent global lock order helps prevent such cycles. The barrier lab forces a particular schedule, making the race reproducible without relying on sleeps or chance.
Why is locking only the assignment insufficient?
Worked solution
The stale read may already have occurred outside the lock.
Common misconceptions
- Concurrency is possible without simultaneous execution.
- A page fault is not necessarily an error or disk operation.
Lab setup
Download each script and run it in a terminal with Python 3.11 or later: python m10_process.py. On Windows, py -3 is an alternative; on some systems use python3. The labs use only the standard library. Predict the result before running, then complete the variations. Run without -O so assertions remain enabled. Outputs below were captured by the builder. Code and output are identical in both language editions.
Lab 1 — Processes and scoped files
Check that the child has a different process ID without embedding machine-dependent IDs in the output.
"""Observe an isolated child process and scoped file cleanup."""
import json, os, subprocess, sys, tempfile
from pathlib import Path
child = subprocess.run([sys.executable, "-c", "import os,json; print(json.dumps({'pid':os.getpid(),'answer':6*7}))"],
check=True, capture_output=True, text=True, timeout=10)
data = json.loads(child.stdout)
assert data["pid"] != os.getpid() and data["answer"] == 42
print("child is a different process:", data["pid"] != os.getpid())
print("child result:", data["answer"])
with tempfile.TemporaryDirectory() as folder:
path = Path(folder) / "catalogue.txt"
path.write_text("Dune\nFoundation\n", encoding="utf-8")
print("file records:", path.read_text(encoding="utf-8").splitlines())
print("temporary directory removed:", not Path(folder).exists())
child is a different process: True
child result: 42
file records: ['Dune', 'Foundation']
temporary directory removed: True
- Make the child exit with a nonzero code.
- Catch CalledProcessError at the caller.
- Explain why the temporary path disappears.
Worked solution
check=True converts a nonzero exit into CalledProcessError; inspect its returncode. TemporaryDirectory's context exit removes its own contents, even when control leaves through an exception.
Lab 2 — Force and repair a race
The barrier ensures both unsafe workers have read the old value before either writes. The safe version locks the full update.
"""Force a lost update deterministically, then protect a critical section."""
from threading import Barrier, Lock, Thread
counter, barrier = [0], Barrier(2)
def unsafe():
old = counter[0]
barrier.wait(timeout=5) # both have read zero before either writes
counter[0] = old + 1
threads = [Thread(target=unsafe) for _ in range(2)]
for t in threads: t.start()
for t in threads: t.join(timeout=10); assert not t.is_alive()
print("forced lost update:", counter[0])
assert counter[0] == 1
counter[0] = 0
lock = Lock()
def safe():
for _ in range(1000):
with lock:
counter[0] = counter[0] + 1
threads = [Thread(target=safe) for _ in range(2)]
for t in threads: t.start()
for t in threads: t.join(timeout=10); assert not t.is_alive()
print("protected updates:", counter[0])
assert counter[0] == 2000
forced lost update: 1
protected updates: 2000
- Draw the forced two-thread interleaving.
- Move the read outside the safe lock and explain the regression.
- Explain why putting the barrier inside a one-thread-at-a-time lock would block progress.
Worked solution
Read A=0, read B=0, then both write 1. A read outside the lock can be stale. If one worker holds the lock while waiting at a two-party barrier, the other cannot acquire the lock to reach it; the timeout exposes the deadlock pattern.
Exercises with worked solutions
Try before opening the solution. ★ applies an idea; ★★ combines ideas; ★★★ asks for design or proof.
Can one program file have two running processes?
Worked solution
Yes; each invocation can have separate state and resources.
Split address 4100 with 4096-byte pages.
Worked solution
Page 1, offset 4.
Jobs A=8 and B=1 arrive together. Compare FCFS A-first with B-first mean turnaround.
Worked solution
A-first completion 8,9 gives 8.5; B-first 1,9 gives 5. Ordering affects mean completion even with identical work.
Two workers read 5 and each writes old+1. Final value?
Worked solution
6, not the intended 7.
Which needs coordination: shared dictionary or thread-local loop index?
Worked solution
Shared mutable dictionary invariants need coordination; independent local indices do not by themselves create a shared-state race.
Two workers need locks X and Y. Give a consistent acquisition rule.
Worked solution
All workers acquire X before Y and release in reverse order. This removes the X→Y versus Y→X circular wait for these locks.
A last copy is borrowed by two callers. What must be atomic?
Worked solution
The availability check and decrement/loan creation must be one coordinated transaction. Locking only the decrement cannot prevent both callers passing the earlier check.
Why close a file even when parsing raises?
Worked solution
Release the resource predictably; a with block arranges cleanup on both normal and exceptional exit.
Self-check quiz
Choose an answer for feedback; reset to retry. A text answer key is available without JavaScript.
Threads in a process share?
Round robin chooses?
A page fault always means disk read?
A lock should cover?
GIL guarantees application transactions?
Consistent lock order helps prevent?
Answer key
- A — Stacks are separate.
- B — It rotates ready work.
- C — Faults can allocate or reject accesses too.
- A — Stale reads outside remain unsafe.
- B — Explicit coordination is still needed.
- C — It addresses a deadlock condition.
Guided reading
- OSTEP processes — Read process states and distinguish ready from running.
- OSTEP locks — Trace a lost update and its protected version.
Review and the next step
Explain exactly how the barrier causes a lost update, then state the locked invariant. Module 11 connects processes through network protocols.
Key terms
| Term | Meaning |
|---|---|
| Context switch | Saving and restoring execution state to change running work. |
| Critical section | Operations requiring coordinated access to shared state. |
| Virtual address | An address interpreted through a process's mapping context. |