Introducción a canales y concurrencia
Esta página presenta los canales para coordinar coroutines dentro de un proceso. Los ejemplos abarcan buffering, selección, flujos productor-consumidor, fan-out, fan-in y cierre de canales.
Clasificación: introducción de referencia/API. Los fragmentos son ejemplos independientes, no una aplicación autónoma.
Contexto y dependencias
Ejecuta estos fragmentos dentro de una función exportada de una entrada Lua ejecutable, como process.lua. Las API channel y coroutine son globales ambientales en ese contexto de ejecución; no necesitan llamadas a require() ni declaraciones modules. Cada fragmento crea sus propios canales y debe evaluarse por separado.
Esta página es una introducción: cada fragmento muestra una API de forma aislada. Péguelos en la función main de una entrada process.lua para ejecutarlos, tal como se configura en el tutorial de Aplicaciones CLI.
Crear Canales
Los canales son tuberías de comunicación para coroutines. Cree con channel.new(capacity):
local ch = channel.new(1) -- buffered channel, capacity 1
Canales con Buffer
Los canales con buffer permiten enviar sin bloquear hasta que el buffer está lleno:
local ch = channel.new(3) -- buffer holds 3 items
-- Send without blocking
ch:send(1)
ch:send(2)
ch:send(3)
-- Receive in FIFO order
local v1, ok1 = ch:receive() -- 1, true
local v2, ok2 = ch:receive() -- 2, true
local v3, ok3 = ch:receive() -- 3, true
Canales sin Buffer
Los canales sin buffer (capacidad 0) sincronizan emisor y receptor:
local ch = channel.new(0) -- unbuffered
local done = channel.new(1)
coroutine.spawn(function()
ch:send("from spawn") -- blocks until receiver ready
done:send(true)
end)
local val = ch:receive() -- receives "from spawn"
local completed = done:receive()
Selección de canales :id=channel-select
channel.select espera en múltiples canales, retorna la primera operación lista:
local ch1 = channel.new(1)
local ch2 = channel.new(1)
ch1:send("ch1_value")
local result = channel.select{
ch1:case_receive(),
ch2:case_receive()
}
-- result is a table with: channel, value, ok
result.channel == ch1 -- true
result.value -- "ch1_value"
result.ok -- true
Selección con envío :id=select-con-send
Use case_send para ofrecer un envío dentro de un select. El caso se elige cuando el canal puede aceptar el valor:
local ch = channel.new(1)
local result = channel.select{
ch:case_send("sent"),
default = true
}
if not result.default then
result.ok -- true (send succeeded)
end
local v = ch:receive() -- "sent"
Select bloquea hasta que uno de sus casos esté listo. Agregue default = true a la tabla de casos para retornar de inmediato en su lugar, con result.default en true cuando nada estaba listo:
local full = channel.new(1)
full:send("first")
local result = channel.select{
full:case_send("second"),
default = true
}
result.default -- true (buffer lleno, nada enviado)
Patrón Productor-Consumidor
Un productor, un consumidor:
local ch = channel.new(5)
local done = channel.new(1)
local consumed = 0
-- Consumer
coroutine.spawn(function()
while true do
local v, ok = ch:receive()
if not ok then break end
consumed = consumed + 1
end
done:send(consumed)
end)
-- Producer
for i = 1, 10 do
ch:send(i)
end
ch:close()
local total = done:receive() -- 10
Patrón Ping-Pong
Sincronizar dos coroutines:
local ping = channel.new(0)
local pong = channel.new(0)
local rounds_done = channel.new(1)
coroutine.spawn(function()
for i = 1, 5 do
ping:receive()
pong:send("pong")
end
rounds_done:send(true)
end)
for i = 1, 5 do
ping:send("ping")
pong:receive()
end
local completed = rounds_done:receive()
Patrón Fan-Out
Un productor, múltiples consumidores:
local work = channel.new(10)
local results = channel.new(10)
-- Spawn 3 workers
for w = 1, 3 do
coroutine.spawn(function()
while true do
local job, ok = work:receive()
if not ok then break end
results:send(job * 2)
end
end)
end
-- Send work
for i = 1, 6 do
work:send(i)
end
work:close()
-- Collect results
local sum = 0
for i = 1, 6 do
local r = results:receive()
sum = sum + r
end
-- sum = (1+2+3+4+5+6)*2 = 42
Patrón Fan-In
Múltiples productores, un consumidor:
local output = channel.new(10)
local producer_count = 4
local items_per_producer = 5
-- Spawn producers
for p = 1, producer_count do
local producer_id = p
coroutine.spawn(function()
for i = 1, items_per_producer do
output:send({producer = producer_id, item = i})
end
end)
end
-- Collect all messages
local received = {}
for i = 1, producer_count * items_per_producer do
local msg = output:receive()
table.insert(received, msg)
end
-- Verify all producers sent their items
local counts = {}
for _, msg in ipairs(received) do
counts[msg.producer] = (counts[msg.producer] or 0) + 1
end
Cerrar Canales
Cierre canales para señalar completación. Los receptores obtienen ok = false cuando el canal está cerrado y vacío:
local ch = channel.new(5)
local done = channel.new(1)
coroutine.spawn(function()
local count = 0
while true do
local v, ok = ch:receive()
if not ok then break end -- channel closed
count = count + 1
end
done:send(count)
end)
for i = 1, 10 do
ch:send(i)
end
ch:close() -- signal no more values
local total = done:receive()
Métodos de Canal
Operaciones disponibles:
channel.new(capacity)— Crea un canal con el tamaño de buffer especificadoch:send(value)— Envía un valor y bloquea si el buffer está lleno; enviar a un canal cerrado genera un errorch:receive()— Recibe un valor y devuelvevalue, okch:close()— Cierra el canal; volver a cerrarlo genera un errorch:case_send(value)— Crea un caso de envío paraselectch:case_receive()— Crea un caso de recepción paraselectchannel.select{cases...}— Espera varias operaciones y devuelvechannel,valueyokchannel.select{cases..., default = true}— Devuelve inmediatamente{default = true, ok = true}si ningún caso está listo
Siguientes Pasos
- Referencia del módulo Channel — Documentación de la API de canales
- Procesos — Comunicación entre procesos