Skip to content

SystemVerilog · Module 14

fork-join_any

SystemVerilog fork-join_any — the variant where the parent resumes when any one thread finishes. The basis for the canonical timeout pattern, watchdog timers, first-responder races, and clock-plus-finite-init startup. The one rule juniors miss: surviving threads do not stop automatically — disable fork.

Module 14 · Page 14.2

fork-join_any is the variant of fork where the parent resumes as soon as the first thread finishes — the rest keep running in the background. This is the IPC tool for timeout patterns, watchdog timers, and first-responder races. The one critical rule juniors miss: the surviving threads do not stop just because the parent moved on. They must be explicitly killed with disable fork. The combination fork ... join_any + disable fork is the canonical timeout idiom in SystemVerilog.

1. Engineering Problem — Why fork-join_any Exists

Every real test needs at least three concurrency patterns that fork-join (the previous lesson) cannot express:

  • A bounded operation. "Wait for the DUT to assert done — but only for at most 100 cycles." If the DUT hangs, the test must fail fast with a clear timeout message, not hang silently until the regression watchdog kills the simulation hours later.
  • A first-responder race. "Watch for ack and error simultaneously — react to whichever arrives first." With fork-join you would have to wait for both, then check which one's flag was set — wasting simulation time when the second signal may never come.
  • A startup sequence with a forever clock. "Generate the clock forever; in parallel, do the finite reset + init sequence; then proceed to the test." fork-join blocks the parent forever because the clock thread never ends.

fork-join_any is the IEEE 1800 answer: spawn all threads, but unblock the parent as soon as any one finishes. The remaining threads continue running unless explicitly killed.

2. Mental Model — Race to the Finish Line

The picture every engineer carries:

fork-join_any is a race. Every thread sprints from the fork bar at the same instant. The parent waits at the join_any bar — but unlike join, the parent crosses the moment the first runner crosses. The other runners are still on the track; they keep going unless someone calls disable fork to pull them off.

Three invariants this picture preserves:

  • First past the post wins. The parent resumes in the Active region of the time slot when the first thread completes. "First" is by $time; ties (multiple threads completing at the same time slot) are resolved by simulator scheduling and must not be relied on.
  • The losers keep running. Threads that have not finished at join_any are now detached — they continue executing until they naturally complete, hit a blocking statement, or are killed by disable fork / disable <named_block>.
  • disable fork is almost mandatory. A fork-join_any without a follow-up disable fork is usually a bug. The one important exception (§8.3) is when a surviving thread is intentionally infinite — a clock generator, a continuous monitor — that you want to keep running.

3. Visual Explanation — The join_any Bar

SystemVerilog fork/join concurrency blockfork-join_any — parent resumes when ANY ONE thread completesFORKThread 1Thread 1do_work(); done = 1Thread 2Thread 2repeat(100) @posedgeclkThread 3Thread 3monitor_for_error()JOIN_ANYwait for ANY one thread

The figure captures the timeout pattern: a useful work thread races a fixed-cycle watchdog and an error-monitor. Whichever finishes first triggers join_any, the parent inspects which thread won (via a flag set inside each thread), and disable fork kills the rest. The JOIN_ANY variant is the warning-amber sibling of join and join_none precisely because of this gotcha-prone "losers keep running" behaviour.

4. Syntax & Semantics — fork, join_any, the Three-Variant Comparison

4.1 The basic form

SystemVerilog — fork-join_any: parent resumes when ANY thread completes
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Syntax ────────────────────────────────────────────────────
fork
    thread_1;
    thread_2;
    thread_3;
join_any
// parent resumes HERE after the FIRST thread completes
// thread_2 and thread_3 (if not finished) continue running in background
 
// ── Concrete example ──────────────────────────────────────────
$display("[MAIN] Starting 3 threads at t=%0t", $time);
fork
    begin
        #10;
        $display("[A] finished at t=%0t (this one triggers join_any)", $time);
    end
    begin
        #30;
        $display("[B] finished at t=%0t (still ran after parent resumed)", $time);
    end
    begin
        #20;
        $display("[C] finished at t=%0t (still ran after parent resumed)", $time);
    end
join_any
$display("[MAIN] Resumed at t=%0t — first thread done", $time);
 
// Output:
//   [MAIN] Starting 3 threads at t=0
//   [A] finished at t=10   ← triggers join_any
//   [MAIN] Resumed at t=10 ← parent unblocks
//   [C] finished at t=20   ← ran in background after parent resumed
//   [B] finished at t=30   ← ran in background after parent resumed

4.2 The three join variants — side-by-side

Questionfork-joinfork-join_anyfork-join_none
Parent resumes when?All threads doneFirst thread doneImmediately (no wait)
Remaining threads?None — all finishedKeep running in backgroundAll running in background
Works with forever loops?No — parent never unblocksYes — finite thread triggers unblockYes — parent never waits
Needs disable fork?No — all threads already doneUsually yes — to kill remaining threadsUsually yes — all threads are background
Lesson14.1 — fork-jointhis page (14.2)14.3 — fork-join_none

5. Simulation View — When the Parent Unblocks, Where the Losers Go

At the fork keyword, the simulator spawns every statement inside the block as a separate process — identical to fork-join. The difference is what happens at join_any:

  1. Each spawned thread runs to completion in its own time. The first to complete records its completion in the simulator's scheduling tables.
  2. The parent is suspended at join_any until the first completion is observed.
  3. The parent resumes in the Active region of the time slot the first thread completed in. Other threads that have not yet completed remain in the scheduler — they continue to be activated by their own delays, event controls, or external signals.
  4. disable fork issued from the parent terminates every thread spawned by the parent (including ones spawned via nested forks). disable <named_block> terminates exactly the named thread.

5.1 Simultaneous completions — race-resolution discipline

If two or more threads complete at exactly the same simulation time step, join_any unblocks the parent at that time slot — but the order in which "first" is decided is not specified by IEEE 1800. Robust code does not depend on which thread the simulator picks; it inspects per-thread flags set inside each thread before exit.

SystemVerilog — robust flag-check pattern for simultaneous completions
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Two threads finish at the same time ───────────────────────
bit a_done = 0, b_done = 0;
 
fork
    begin #100; a_done = 1; end   // both finish at t=100
    begin #100; b_done = 1; end
join_any
disable fork;
 
// ❌ WRONG: only checks one flag — assumes mutual exclusion
if      (a_done) $display("A triggered");
else if (b_done) $display("B triggered");
 
// ✅ CORRECT: check all flags — any might be set, possibly both
if (a_done) $display("A was done");
if (b_done) $display("B was done");

A more robust pattern uses a shared winner variable that records which thread crossed the line first by inspecting the variable's not-yet-written state.

SystemVerilog — winner-takes-it-once pattern for first-responder races
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
string winner = "";
 
fork
    begin
        do_work();
        if (winner == "") winner = "work";       // first one sets it
    end
    begin
        repeat(1000) @(posedge clk);
        if (winner == "") winner = "timeout";    // loses if work already set it
    end
join_any
disable fork;
$display("[TB] First to complete: %s", winner);

6. Waveform — Parent Unblocks at First Completion, Losers Run On

The waveform below shows the three-thread example from §4.1. Threads A, B, C all start at cycle 0. A finishes at cycle 2 → join_any unblocks the parent immediately. C and B keep running in the background — C finishes at cycle 4, B at cycle 6 — but the parent has already moved on and is running its post-fork work concurrently with them.

Figure — fork-join_any: parent resumes when A finishes; C and B keep running in background

12 cycles
Figure — fork-join_any: parent resumes when A finishes; C and B keep running in backgroundA done → parent unblocksA done → parent unbloc…C done (background)C done (background)B done (background — `disable fork` missed!)B done (background — `…clkthread ARUNRUNdonedonedonedonedonedonedonedonedonedonethread BRUNRUNRUNRUNRUNRUNdonedonedonedonedonedonethread CRUNRUNRUNRUNdonedonedonedonedonedonedonedoneparentBLOCKEDBLOCKEDRESUMERUNRUNRUNRUNRUNRUNRUNRUNRUNt0t1t2t3t4t5t6t7t8t9t10t11
At cycle 0 the parent forks three threads. A finishes at cycle 2 — join_any unblocks the parent in the same time slot. C (cycle 4) and B (cycle 6) keep running while the parent is already doing post-fork work. Without disable fork, those background threads continue executing — that is the source of the false-watchdog-fatal bug.

The amber marker at cycle 6 is the warning: B was still alive and writing to signals / variables / mailboxes the whole time the parent thought the fork was "over." If B's body contains a $fatal watchdog, the test fails three cycles after it actually passed.

7. Synthesis — Not Applicable

fork-join_any is a procedural simulation construct. It has no hardware footprint and no place in synthesisable RTL — synthesis tools reject it outright. This section is intentionally omitted; the topic does not warrant it.

The corresponding hardware idiom for "first-responder among N signals" is an OR-tree of assign first_done = a_done | b_done | c_done; — combinational logic that asserts when any input asserts. That is built into RTL semantics, not a procedural primitive.

8. Verification View — The Canonical Patterns

8.1 The timeout pattern — the most important use case

SystemVerilog — timeout pattern: task races against a deadline
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Pattern: task must complete within 100 clock cycles ───────
bit task_done = 0;
 
fork
    // ── Thread 1: the real work
    begin : work
        do_long_operation();
        task_done = 1;             // mark success BEFORE the thread exits
    end
 
    // ── Thread 2: the timeout watchdog
    begin : watchdog
        repeat (100) @(posedge clk);
    end
join_any
disable fork;                      // kill whichever thread didn't win
 
// Check which one triggered join_any
if (task_done)
    $display("[TB] Operation completed in time at %0t", $time);
else
    $fatal(1, "[TB] TIMEOUT: operation did not complete within 100 cycles");

Critical: the success flag (task_done) must be set inside the work thread before it exits, not after the join_any. By the time the parent reaches the post-fork code, the work thread is already terminated and any assignment to task_done outside it would race with disable fork.

8.2 The forgotten-disable-fork bug — wrong / right

❌ Wrong — no disable fork, false watchdog fatal
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fork
    begin
        do_work();
        done = 1;
    end
    begin
        repeat(100) @(posedge clk);
        $fatal(1, "Timeout!");
    end
join_any
// work finished first — done=1, parent
// resumes — BUT watchdog is STILL
// counting in the background!
// 100 cycles later: $fatal fires
// even though the test ALREADY PASSED.

False timeout fatal — test aborts incorrectly with confusing log.

✅ Right — disable fork kills the loser
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fork
    begin : work
        do_work();
        done = 1;
    end
    begin : watchdog
        repeat(100) @(posedge clk);
        $fatal(1, "Timeout");
    end
join_any
disable fork;   // kills whichever
                // thread is still running

If work finished first: watchdog killed cleanly. If watchdog fired first: $fatal runs, work killed.

8.3 The legitimate exception — clock-plus-finite-startup

fork ... join_any paired with disable fork is the rule. The important exception is when one thread is intentionally infinite and should keep running after the parent unblocks — most commonly a clock generator or a continuous monitor.

SystemVerilog — clock keeps running; do NOT disable fork after join_any
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Clock runs forever; reset + init are finite ───────────────
// fork-join would block forever (clock never ends).
// fork-join_any unblocks when the FINITE thread (reset+init) finishes.
fork
 
    // ── Thread 1: clock generator (forever — never terminates)
    begin : clk_gen
        clk = 0;
        forever #5 clk = ~clk;
    end
 
    // ── Thread 2: reset + init sequence (finite)
    begin : startup
        rst_n = 0;
        repeat(4) @(posedge clk);
        rst_n = 1;
        repeat(2) @(posedge clk);
        init_dut();
        $display("[TB] Startup complete at %0t", $time);
    end
 
join_any
// Do NOT disable fork here — the clock generator must keep running!
// Only the 'startup' thread exits; the parent continues with the clock still active.
$display("[TB] Proceeding to test with clock running");
run_test();

8.4 Global test watchdog

SystemVerilog — global watchdog: test or timeout, whichever comes first
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Test body races against a global watchdog ─────────────────
initial begin
    fork
 
        // ── The actual test
        begin : test_body
            reset_and_init();
            run_stimulus(500);       // 500 transactions
            check_results();
            $display("[TB] TEST PASSED at %0t", $time);
        end
 
        // ── Global watchdog: 1 million cycles max
        begin : watchdog
            repeat (1_000_000) @(posedge clk);
            $fatal(1, "[WDG] Test timed out after 1M cycles at %0t", $time);
        end
 
    join_any
    disable fork;       // kill watchdog if test passed, or kill test if watchdog fired
    $finish;
end

8.5 First-responder race

SystemVerilog — wait for ACK or ERROR, react to whichever arrives first
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
bit got_ack = 0, got_err = 0;
 
fork
    begin                              // thread 1: wait for ACK
        @(posedge clk iff ack);
        got_ack = 1;
    end
    begin                              // thread 2: wait for ERROR
        @(posedge clk iff error_flag);
        got_err = 1;
    end
join_any
disable fork;
 
if      (got_ack) $display("[TB] ACK received — success");
else if (got_err) $error  ("[TB] ERROR flag seen — protocol violation");
 
// ── Wait for any of N events ──────────────────────────────────
event ch_done [4];
int   first_ch = -1;
 
fork
    begin @ch_done[0]; if (first_ch == -1) first_ch = 0; end
    begin @ch_done[1]; if (first_ch == -1) first_ch = 1; end
    begin @ch_done[2]; if (first_ch == -1) first_ch = 2; end
    begin @ch_done[3]; if (first_ch == -1) first_ch = 3; end
join_any
disable fork;
$display("[TB] Channel %0d completed first", first_ch);

Note the if (first_ch == -1) guard inside each thread — protects against the simultaneous-completion race from §5.1 where two channels might finish in the same time slot and both try to write first_ch.

8.6 Phase completion with max-cycles cap

SystemVerilog — run stimulus or up to MAX cycles, whichever is first
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
parameter MAX_CYCLES = 50_000;
bit stimulus_done = 0;
 
fork
    begin
        send_all_transactions();
        stimulus_done = 1;
    end
    begin
        repeat (MAX_CYCLES) @(posedge clk);
    end
join_any
disable fork;
 
if (!stimulus_done)
    $warning("[TB] Max cycles reached — %0d of %0d txns sent", sent_count, total_txns);
 
// Drain: let last transactions propagate before scoreboard check
repeat (100) @(posedge clk);
check_scoreboard();
$finish;

9. Industry Usage — Where fork-join_any Lands in Real Verification

  • UVM phase timeouts — every uvm_test ships with a uvm_phase::set_timeout() mechanism that internally uses fork-join_any with the user's run_phase() racing against a deadline. The 100% standard idiom.
  • UVM uvm_event::wait_trigger_data() + timeout — common pattern: wait for an event, fall back to a timeout $fatal if it does not fire within a bound.
  • AXI / DDR / PCIe response-or-error races — every protocol VIP's monitor uses a fork-join_any to wait for either the expected response cycle or an out-of-spec response (parity error, retry, abort).
  • Regression-level test watchdogs — every test in every shop's regression has an outer fork-join_any with a global cycle cap; without it, hung simulations bring the LSF grid to its knees.
  • Reset bring-up tests — clock generator (forever) + finite reset / init sequence, exactly the §8.3 pattern. The most common join_any use that legitimately omits disable fork.
  • OCV / SDF / scan-mode tests — multi-mode tests where one mode terminates the other in a race-to-completion pattern.

10. Design Review Notes — What a Senior Will Flag

Pattern in the diffWhat review will say
fork ... join_any with no following disable fork and the threads are not intentionally infinite"Surviving threads keep running. Whatever they do — fire a $fatal, write to a shared variable, push to a mailbox — will happen after the fork 'ended.' Add disable fork immediately after join_any, or document why the survivors must persist."
fork ... join_any with $fatal inside the watchdog branch and no disable fork"Classic false-watchdog-fatal bug. If the work thread wins, the watchdog keeps counting and fires $fatal later. Always pair join_any + disable fork when any thread has a $fatal / $error."
Flag set after the join_any, not inside the thread"Race — by the time the parent reaches the post-fork code, the thread may already be dead (killed by disable fork). Set the flag inside the thread before the thread's last statement."
else if chain checking which thread won (without considering simultaneous completion)"Two flags may both be set if threads complete in the same time slot. Use independent if checks, not mutually-exclusive else if."
disable fork after a join_any that has a clock-generator branch"You just killed the clock. Every clocked process in the design is now frozen. Either don't disable fork, or use named-block disable <work_block_only>."
Anonymous fork branches in a join_any block"Name the branches — debug logs show anonymous branches as process_N. Named blocks also enable selective disable <name> instead of the nuclear disable fork."
fork ... join_any with one thread that contains @event (not wait(event.triggered))"If the trigger may fire at the same time the thread arms, the @ will miss it and the race becomes scheduling-dependent. Use wait(event.triggered) inside join_any branches for any one-shot event."
wait fork; after a join_any block in the same parent"wait fork blocks until every previously-spawned thread completes — including the survivors of the join_any. If you wanted that you should have used fork-join in the first place. Probably a leftover; remove."

The single highest-value rule: fork-join_any + disable fork is one idiom, not two. Reviewing diffs for join_any without a following disable fork (with no intentional-infinite-thread comment) catches 80% of the bugs this construct produces.

11. Debugging Guide — Real Failures, Real Fixes

1

Test passes but $fatal fires 100 cycles later

FALSE-WATCHDOG-FATAL
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fork
    begin do_work(); done = 1; end
    begin
        repeat(100) @(posedge clk);
        $fatal(1, "Timeout");
    end
join_any
// no disable fork
Symptom
Log shows "work complete, test passed" — then 100 cycles later $fatal: "Timeout" fires from the watchdog. The test result is FAIL despite the test having succeeded. Regression report misclassifies the test.
Root Cause
Missing disable fork. When the work thread wins, the watchdog timer keeps counting in the background. After 100 cycles its $fatal runs, aborting the simulation. The "test passed" log line was correct — the fail is purely the orphaned watchdog.
Fix
Add disable fork; immediately after join_any. This is the canonical timeout idiom: fork {work} {watchdog} join_any disable fork. Lint rules at every shop flag join_any without an immediately-following disable fork (with comment-allowed exceptions).
2

Reset bring-up test hangs at t=0 forever

JOIN-WITH-FOREVER
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fork
    begin
        clk = 0;
        forever #5 clk = ~clk;
    end
    begin
        reset_and_init();
    end
join                       // ← wrong: forever thread never ends
Symptom
Simulator runs to the configured timeout (or grid OOM) with no useful log output. $finish never executes. Looks like a DUT hang; it isn't — the test never reached the DUT.
Root Cause
fork-join blocks the parent until every thread completes. The clock-generator branch is forever — it never completes. The parent waits indefinitely.
Fix
Replace join with join_any. The finite reset+init thread triggers join_any; the clock keeps running (intentional infinite); do not add disable fork. This is the canonical clock-plus-startup pattern.
3

Watchdog kills the clock; every clocked assertion fails afterward

DISABLE-FORK-KILLED-CLOCK
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fork
    begin clk = 0; forever #5 clk = ~clk; end
    begin reset_and_init(); end
join_any
disable fork;              // BUG: kills the clock!
run_test();                // every @(posedge clk) hangs
Symptom
Startup phase looks correct in the log. Then every subsequent @(posedge clk) blocks forever. Simulator timeout fires. Verdi shows the clock frozen at its last value after the startup sequence.
Root Cause
disable fork after join_any killed every thread spawned by the parent — including the intentional-infinite clock generator. The clock signal stops toggling; all clocked processes freeze.
Fix
Remove the disable fork. The clock generator is intended to keep running. If you do need to kill only the startup thread (you don't, since it already finished), use disable startup_block_name with the startup block named.
4

First-responder code thinks neither thread won

FLAG-SET-AFTER-EXIT
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
bit a = 0, b = 0;
fork
    begin wait_for_a(); end
    begin wait_for_b(); end
join_any
disable fork;
a = 1;                     // BUG: set AFTER fork, on the parent
b = 0;
if (!a && !b) $error("neither?");
Symptom
Test occasionally logs "neither?" even when one of the wait conditions clearly fired. The flag assignments do not match which thread won.
Root Cause
Flags are set on the parent after the fork ends, not inside the threads before they exit. By that time disable fork has already killed any pending thread state. The parent has no way to know which condition was actually observed.
Fix
Set the flag inside the thread and before the thread's last statement: begin wait_for_a(); a = 1; end. The flag is now set in the thread that observed the condition, before the thread terminates, and survives disable fork.
5

Two parallel channels both set winner; race-detect logs wrong winner

SIMULTANEOUS-COMPLETION
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
bit a_done = 0, b_done = 0;
fork
    begin #100; a_done = 1; end
    begin #100; b_done = 1; end
join_any
disable fork;
if (a_done) $display("A won");
else if (b_done) $display("B won");
Symptom
Log says "A won" sometimes and "B won" other times for the same seed across simulator versions. The else if implies mutual exclusion, but both flags are actually set when the threads complete in the same time slot.
Root Cause
When two threads complete at the same $time, both reach their flag-setting statement before join_any resolves. Both a_done and b_done are 1. The else if chain only logs the first one checked — misleadingly suggesting only one fired.
Fix
Either (a) accept that simultaneous wins are real and check all flags with independent if statements, or (b) use a shared winner variable that is set inside the first thread to reach it, guarded by if (winner == ""). The latter gives a deterministic "first crosser" semantic when the simulator's pick is stable, and a single chosen winner when it isn't.

12. Interview Insights — What Interviewers Actually Probe

fork-join_any spawns all threads simultaneously, identical to fork-join. The parent suspends at join_any and resumes in the Active region of the time slot when the first thread completes. The remaining threads continue running as background processes — they are not stopped, paused, or cancelled. They will execute until they naturally complete, hit a blocking statement, or are explicitly killed with disable fork or disable <named_block>.

13. Exercises

1. Design — bounded register write (Foundation)
Write a write_with_timeout(int addr, data, int max_cycles) task that drives a register write and returns success / fail based on whether ack asserts within max_cycles. Use fork-join_any + disable fork and set the success flag inside the work thread before it exits.

2. Debug — the false watchdog fatal (Intermediate)
A teammate's test consistently logs "test passed" then 50 cycles later prints $fatal: "TIMEOUT" and the regression marks the test as failed. The fork is fork begin do_work(); done=1; end begin repeat(50) @(posedge clk); $fatal(1, "TIMEOUT"); end join_any. Identify the bug and write the one-line fix.

3. Code review — the clock-killer (Intermediate)
A teammate's startup sequence is fork begin forever #5 clk = ~clk; end begin reset_and_init(); end join_any disable fork; followed by run_test(). The startup log looks correct, then every subsequent @(posedge clk) hangs. Identify the bug, predict the symptom in the simulator log, and write the fix.

4. Trade-off — fork-join_any + disable fork vs fork-join_none + wait fork (Advanced)
A teammate argues these two patterns are equivalent — both spawn N threads and don't wait for all of them. Argue the case against. Construct a scenario where fork-join_any + disable fork gives the correct semantics and fork-join_none + wait fork does not (or vice versa). Explain the underlying scheduler difference.

14. Summary

fork-join_any resumes the parent the moment the first thread completes; the rest keep running in the background. The single discipline you must internalise is fork ... join_any + disable fork as one idiom — without disable fork, surviving threads keep firing assertions, writing variables, and ticking watchdogs. The one important exception is when a surviving thread is intentionally infinite (clock generator); then omit disable fork deliberately.

Defaults to memorise. fork {work} {watchdog} join_any disable fork; is the canonical timeout idiom. Set success flags inside the work thread, before the thread's last statement — not on the parent after the fork. Check all per-thread flags with independent if checks, not else if chains. Name every branch you might want to debug or selectively disable.

Next up: 14.3 — fork-join_none — the variant where the parent does not wait at all. The basis for daemon threads, fire-and-continue stimulus, and the wait fork; deferred-join pattern.