Saltar al contenido

Concurrencia Estructurada, Select y Worker Pool

En esta página

scope { ... } implementa concurrencia estructurada: todos los spawn dentro del bloque son esperados antes de continuar. Si algún hijo entra en pánico, sus hermanos son cancelados. Esto elimina tareas huérfanas y garantiza que los recursos liberados tras el scope estén realmente limpios.

Dos productores y un consumidor en el mismo scope; el programa solo avanza después de que todos terminen.

12-scope-spawn.zolo
Playground
// Feature: structured concurrency — `scope { spawn ... }` joins on exit

// Syntax: `scope { ... }` spawns child tasks and waits for all of

// them before continuing. A panic in any child cancels its siblings.

// When to use: any group of related coroutines that should live and

// die together. Avoids dangling tasks and orphaned futures.


let ch = channel(0)
scope {
    spawn {
        ch.send("first")
    }
    spawn {
        ch.send("second")
    }
    spawn {
        let a = ch.recv()
        let b = ch.recv()
        // Sort for deterministic output regardless of scheduler order.

        let xs = [a.__val, b.__val]
        if xs[0] < xs[1] {
            print(xs[0])
            print(xs[1])
        } else {
            print(xs[1])
            print(xs[0])
        }
    }
}
print("scope-exited")
// expected:

//   first

//   second

//   scope-exited

select espera en múltiples canales al mismo tiempo y dispara el primer brazo listo. Añade after <duración> => { ... } para timeout y default => { ... } para polling no bloqueante.

Selección entre dos canales, timeout con after 100ms y poll inmediato con default.

13-select.zolo
Playground
// Feature: `select` — wait on multiple channels at once

// Syntax:

//   select {

//       x := <- chA => ...

//       y := <- chB => ...

//       after 50ms => ...

//       default => ...

//   }

// Fires the first arm that is ready. `select` blocks, so it must

// run inside a coroutine. `after Ns` adds a timeout. `default`

// fires immediately if nothing is ready.

// When to use: multi-source consumers, timeouts, non-blocking polls.


let a = channel(1)
let b = channel(1)
scope {
  spawn {
    sleep 1ms
    a.send("from-a")
  }
  spawn {
    // Only `a` will be ready, so this arm fires.

    select {
      x := <- a => { print("a: {x}") }
      y := <- b => { print("b: {y}") }
    }
  }
}

// expected: a: from-a


// Timeout via `after <duration>`.

let c = channel(1)
scope {
  spawn {
    select {
      x := <- c => { print("got: {x}") }
      after 100ms => { print("timeout") }
    }
  }
}

// expected: timeout


// Non-blocking poll via `default`.

let d = channel(1)
scope {
  spawn {
    select {
      x := <- d => { print("got: {x}") }
      default => { print("nothing-ready") }
    }
  }
}
// expected: nothing-ready

El patrón worker pool combina todo: un productor distribuye trabajo por un canal jobs, N workers compiten por ítems y envían resultados al canal results, y un agregador consume los resultados. channel(0) (rendezvous) aplica contrapresión en todo el pipeline automáticamente.

1 productor → 4 workers → 1 agregador, con scope garantizando el join de todas las tareas.

14-worker-pool.zolo
Playground
// Feature: worker pool — fan-out + fan-in over channels

// Pattern: 1 producer feeds a `jobs` channel, N workers compete for jobs

// and push results into a `results` channel, 1 aggregator drains them.

// `scope { }` joins everything: when the last spawn finishes, the block

// exits. With `channel(0)` (rendezvous) you get backpressure for free —

// workers block on send until the aggregator is ready, and the producer

// blocks on send until a worker is ready.


let jobs = channel(0)
let results = channel(0)
let total = 8

scope {
    spawn {
        for i in 0..total { jobs.send(i) }
        jobs.close()
    }

    // 4 workers — each loops on `for n in jobs` until the channel closes.

    spawn { for n in jobs { results.send(n * n) } }
    spawn { for n in jobs { results.send(n * n) } }
    spawn { for n in jobs { results.send(n * n) } }
    spawn { for n in jobs { results.send(n * n) } }

    // Aggregator: knows how many results to expect, closes the channel

    // when the last one lands so the scope can complete.

    spawn {
        var got = 0
        for r in results {
            print("result: {r}")
            got += 1
            if got == total { results.close() }
        }
    }
}
print("done")
// expected (in some interleaved order — squares of 0..7 then "done"):

//   result: 0, 1, 4, 9, 16, 25, 36, 49

//   done

Los deadlocks son ruidosos, no silenciosos. Un scope cuyas tareas quedan todas bloqueadas en canales sin ningún desbloqueador posible lanza deadlock: ... en lugar de colgarse indefinidamente.

Buscar en Zolo

9 resultados

enespt-br