Pular para o conteúdo

23 · Concorrência

Concorrência Estruturada, Select e Worker Pool

scope aguarda todos os filhos; select espera em múltiplos canais; worker pool implementa fan-out + fan-in.

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.

Índice global

Encontre seu caminho pela Zolo

Experimente uma ideia

Comece por aqui

9 resultados

9 resultados

enespt-br