03 · Fork and join semantics
Choose when the parent continues and who waits for the rest.
join, join_any, and join_none all create child processes; they differ only in the condition that releases the parent. That condition changes start time, variable capture, and cleanup obligations.
Parent release condition
Same children, different continuation
- →
- →
- 01joinall finish
Parent resumes after the slowest immediate child.
- 02join_anyfirst finishes
Other children continue unless explicitly stopped or awaited.
- 03join_noneparent does not wait
Children begin when the parent next blocks or terminates.
Parent resumes after the slowest immediate child.
- Child start
- Parent yields
- With join_none, creation precedes execution until the parent blocks or ends.
- Loop capture
- automatic local
- Create per-iteration storage inside the forked context.
- Same-time wakeup
- Potential race
- No deterministic order exists within the same event region.
A fork creates topology. The join keyword states only when the parent resumes, not how surviving children are managed.
Observe
Mark the parent process, each immediate child, the first statement after the fork, and which child lifetimes may outlast local variables.
Decide
Select a join variant from the required parent-release condition, then add wait fork, process handles, or scoped cancellation for remaining children.
Failure
join_any leaves siblings running, and join_none loop bodies can all observe the loop's final value unless each iteration captures an automatic local.
Prove
Draw absolute start/finish times, log parent and child IDs, randomize delays, and assert every spawned child reaches completion or a reviewed cancellation path.
join_none startup is deferred until the parent yields
The parent schedules children and continues. Children start when the parent reaches a blocking statement or terminates; an artificial #0 is not required if a real blocking point already exists. Without one, a loop can finish spawning before any child runs.
for (int i = 0; i < 8; i++) begin
fork
automatic int lane = i;
drive_lane(lane);
join_none
end
wait fork; // waits for this process's immediate outstanding childrenSame-time branches have no source-order priority
When two child processes wake in the same event region, their execution order is not defined. A branch that writes x with blocking assignment and another that evaluates y <= x can capture either old or new x depending on scheduling.
| Intent | Construct | Required follow-up |
|---|---|---|
| Parallel work, then combine all | fork…join | None beyond result checks |
| Race worker versus timeout | join_any | Cancel or await survivor |
| Launch background workers | join_none | Store handles or later wait fork |
| Iterative launch | join_none + automatic capture | Bound count and completion |
| One branch containing sequential steps | begin…end inside fork | Use absolute timeline |
Explain it out loud
Interview reasoning checkpoints
They start when the parent process next blocks or terminates. The fork statement creates them, but the parent continues until it yields.
- Find the first blocking statement after the fork.
- Determine what shared variables change before that point.
- Capture per-child values automatically before relying on them.
This tests scheduler knowledge and explains common loop-capture bugs.
Adding #0 reflexively and assuming it creates a portable priority.
They continue running. The parent must deliberately await, cancel, or transfer ownership of every survivor.
- Identify which child completed first.
- List all remaining live children.
- Use handles or a narrow named scope to clean them up without killing unrelated work.
The key is that join_any is not race-and-cancel.
Calling disable fork without checking the caller's full child scope.
