# Step 7 — Concurrency `latprobe` now probes multiple URLs in parallel, cutting wall-clock time for large runs without sacrificing measurement accuracy. ## How it works | Axis | Behaviour | |------|-----------| | Across URLs | **Parallel** — up to `-c` workers run simultaneously | | Samples of one URL (`-n`) | **Sequential** — always serial within a URL | Keeping samples sequential ensures that min/avg/max statistics for a single URL reflect genuine latency variability, not artificial load created by firing multiple requests at the same server at once. Output is always printed in **input order**, regardless of which URL finishes first. ## Flag ``` -c, --concurrency int Max URLs probed in parallel (0 = auto, default) ``` | Value | Behaviour | |-------|-----------| | `0` (default) | Auto: `min(numURLs, 8)` — scales with workload, caps at 8 | | `1` | Fully serial — identical to the old behaviour; best for precision | | `N > 1` | Explicit worker cap | ## When to use serial mode For the most accurate latency numbers — especially when comparing sites — use `-c 1`. Concurrent probes share your local NIC, DNS resolver, and CPU, which can inflate timings on slower machines or fast batch runs. ```sh # High fidelity: one URL at a time ./go/latprobe -c 1 -n 10 https://www.google.com https://www.bbc.co.uk # Speed: all four probed in parallel (auto default would do this anyway) ./go/latprobe -c 4 -n 10 https://www.google.com https://www.bbc.co.uk \ https://www.lemonde.fr https://www.abc.net.au ``` ## Example: speedup in practice Five sites, 5 samples each — sequential vs parallel: ```sh # Serial (-c 1): ~wall time ≈ sum of all RTTs × 5 time ./go/latprobe -c 1 -n 5 \ https://www.google.com https://www.bbc.co.uk \ https://www.lemonde.fr https://www.spiegel.de https://www.abc.net.au # real ~1m15s (5 sites × ~15s sequential) # Parallel (default): ~wall time ≈ slowest single URL × 5 time ./go/latprobe -n 5 \ https://www.google.com https://www.bbc.co.uk \ https://www.lemonde.fr https://www.spiegel.de https://www.abc.net.au # real ~18s (all 5 measured at once, gated by the slowest) ``` ## Exit codes Exit codes work exactly as before: when multiple failure types occur the **highest** code wins. Concurrency does not change this — exit codes are accumulated after all workers complete. ## Race safety The implementation uses a semaphore channel (`chan struct{}`) and `sync.WaitGroup` with each goroutine writing only its own indexed result slot, so there is no shared mutable state. `http.DefaultClient` is safe for concurrent use. Verified clean with `go test -race` (`make go-test-race`).