---
name: parallel_for
kind: function
lang: cpp
domain: core
version: "1.0.0"
purity: impure
signature: "ThreadPool::ThreadPool(int n_threads = 0); void ThreadPool::parallel_for(size_t begin, size_t end, fn(i)); void ThreadPool::parallel_for_chunks(size_t begin, size_t end, fn(tid, lo, hi))"
description: "Pool de threads reutilizable con parallel_for por indice y parallel_for_chunks para acumulado por thread. Reparte rango contiguo por hw concurrency. Captura excepciones del worker y las relanza en el caller. Para Monte Carlo CPU multi-core."
tags: [thread, parallel, concurrency, montecarlo, core]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [thread, mutex, condition_variable, atomic, queue, vector, functional, exception]
tested: false
tests: []
test_file_path: ""
file_path: "cpp/functions/core/parallel_for.cpp"
params:
  - name: n_threads
    desc: "Numero de workers. 0 = hardware_concurrency. El destructor joinea todos."
  - name: begin
    desc: "Indice inicial del rango (inclusive)."
  - name: end
    desc: "Indice final del rango (exclusive). end <= begin es no-op."
  - name: fn
    desc: "Functor a ejecutar. parallel_for: void(size_t i). parallel_for_chunks: void(int tid, size_t lo, size_t hi) — el chunk [lo, hi) lo procesa el thread tid."
output: "El pool ejecuta el functor en paralelo y bloquea el caller hasta que todos los chunks terminan. Si algun worker lanza una excepcion, se captura la primera y se relanza desde el caller tras el join."
---

# parallel_for

Pool minimo para Monte Carlo CPU. Una instancia mantiene workers vivos entre llamadas — barato hacer muchas N pequenas (no hay overhead de spawn por llamada).

## Patron tipico — N sesiones independientes

```cpp
fn::ThreadPool pool;        // hardware_concurrency workers

std::vector<double> pnl(N);
pool.parallel_for(0, N, [&](std::size_t i) {
    fn::ds::Rng r;
    fn::ds::rng_seed(r, /*master=*/0xC0FFEE ^ i);   // seed por sesion
    pnl[i] = simulate_session(r);
});
```

## Patron — accumulado por thread (sum / hist local)

`parallel_for_chunks` evita atomic contention dando un slice contiguo a cada thread:

```cpp
std::vector<double> partials(pool.n_threads(), 0.0);

pool.parallel_for_chunks(0, N,
    [&](int tid, std::size_t lo, std::size_t hi) {
        double local = 0.0;
        for (std::size_t i = lo; i < hi; ++i) {
            local += compute(i);
        }
        partials[tid] = local;
    });

double total = std::accumulate(partials.begin(), partials.end(), 0.0);
```

## Notas

- **Throughput**: 8 cores @ 4 GHz, jobs de ~10us cada uno => ~5x speedup tipico (Amdahl: lo bloquea el job mas largo + cv-wait coste). Para jobs <1us el coste de submit domina; usar `parallel_for_chunks` con chunk grande.
- **Excepciones**: solo se preserva la PRIMERA excepcion. Las demas se silencian (con la garantia de que el resto de los workers terminan limpiamente).
- **No re-entrante**: un worker no puede llamar `parallel_for` sobre el mismo pool — bloquearia el done_cv. Si se necesita anidamiento, crear un segundo pool.
- **Comparar con GPU**: para 10^6 sesiones de 10^4 spins (= 10^10 ops MC), CPU 8-core ~10s, GPU ~1-2s. CPU pool es la fallback portable; GPU es para el caso extremo.
