Pular para o conteúdo

Concorrência Estruturada, Select e Worker Pool

Nesta página

scope { ... } implementa concorrência estruturada: todos os spawn dentro do bloco são aguardados antes de continuar. Se qualquer filho entrar em pânico, os irmãos são cancelados. Isso elimina tarefas órfãs e garante que os recursos liberados após o scope estejam realmente limpos.

Dois produtores e um consumidor num mesmo scope; o programa só avança após todos terminarem.

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 aguarda múltiplos canais ao mesmo tempo e dispara o primeiro braço pronto. Adicione after <duração> => { ... } para timeout e default => { ... } para polling não-bloqueante.

Seleção entre dois canais, timeout com after 100ms e poll imediato com 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

O padrão worker pool combina tudo: um produtor distribui trabalho por um canal jobs, N workers competem por itens e enviam resultados ao canal results, e um agregador consome os resultados. channel(0) (rendezvous) aplica contrapressão em todo o pipeline automaticamente.

1 produtor → 4 workers → 1 agregador, com scope garantindo o join de todas as tarefas.

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

Deadlocks são ruidosos, não silenciosos. Um scope cujas tarefas ficam todas bloqueadas em canais sem nenhum desbloqueador possível lança deadlock: ... em vez de travar para sempre.

Buscar no Zolo

9 resultados

enespt-br