# "Channels and Coroutines"
_Path: en/lua/core/channel_
> "Go-style channels for inter-coroutine communication. Create buffered or unbuffered channels, send and receive values, and coordinate between…"
## Table of Contents
- Channels and Coroutines
## Content
# Channels and Coroutines
Channels exchange values between concurrent tasks. They can be buffered or unbuffered and can be combined with `channel.select` to coordinate multiple operations.
This is an API reference. The basic blocks are isolated snippets; the timeout, fan-in, and non-blocking sections are partial patterns whose named channels and callbacks come from the surrounding application. The worker-pool block is a complete in-process example.
The `channel` and `coroutine` globals are always available. Channels coordinate coroutines within one Lua process; use process messaging, functions, or queues across process boundaries.
## Creating Channels
An unbuffered channel (size 0) requires a sender and receiver to be ready before a transfer completes. A buffered channel allows sends to complete while buffer space is available.
```lua
-- Unbuffered: synchronizes sender and receiver
local sync_ch = channel.new()
-- Buffered: queue up to 10 messages
local work_queue = channel.new(10)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `size` | integer | Buffer capacity (default: 0 for unbuffered) |
**Returns:** `channel`
## Sending Values
Sending blocks until a receiver is ready on an unbuffered channel or until buffer space is available on a buffered channel.
```lua
-- Send work to a worker pool
local tasks = {"task-a", "task-b"}
local jobs = channel.new(100)
for i, task in ipairs(tasks) do
jobs:send(task) -- Blocks if buffer full
end
jobs:close() -- Signal no more work
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `value` | any | Value to send |
**Returns:** `boolean`
Sending to a closed channel raises an error.
## Receiving Values
Receiving blocks until a value is available or the channel is closed.
```lua
-- Worker consuming from job queue
while true do
local job, ok = jobs:receive()
if not ok then
break -- Channel closed, no more work
end
process(job)
end
```
Here, `jobs` is the application-provided queue and `process` is its task-processing callback.
**Returns:** `any, boolean`
- `value, true` — a value was received
- `nil, false` — the channel is closed and empty
## Closing Channels
Closing a channel causes pending senders to receive an error and pending receivers to receive `nil, false`. Closing an already closed channel is a no-op.
```lua
local results = channel.new(10)
-- Producer fills results
for _, item in ipairs(data) do
results:send(process(item))
end
results:close() -- Signal completion
```
This isolated producer snippet assumes `data` and the `process` callback are provided by the application.
## Selecting from Multiple Channels
`channel.select` waits on multiple channel operations at the same time. It can coordinate event sources, timeouts, and non-blocking checks.
```lua
local result = channel.select(cases)
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `cases` | table | Array of select cases |
| `default` | boolean | If true, returns immediately when no case ready |
**Returns:** `table`
- For a channel case: `{channel, value, ok}` — `channel` is the case's channel, `value` is the received/sent value, `ok` is false for a closed-channel receive.
- For the default branch (when no case is ready and `default = true`): `{default = true, ok = true}`.
### Timeout Pattern
Use `time.after()` to add a timeout to a channel wait.
```lua
local time = require("time")
local result_ch = application_response_channel
local timeout, err = time.after("5s")
if err then
return nil, err
end
local r = channel.select {
result_ch:case_receive(),
timeout:case_receive()
}
if r.channel == timeout then
return nil, errors.new({ kind = errors.TIMEOUT, message = "Operation timed out" })
end
return r.value
```
This partial pattern assumes the entry lists `time` in `modules:` and the application supplies `application_response_channel`. `time.after` returns one channel on success; invalid or non-positive durations return `nil, error`.
### Fan-in Pattern
Handle values from multiple sources in one loop.
This process-entry pattern uses ambient `process`, while the application supplies the shutdown signal and the two handler functions.
```lua
local events = process.events()
local inbox = process.inbox()
local shutdown = channel.new()
while true do
local r = channel.select {
events:case_receive(),
inbox:case_receive(),
shutdown:case_receive()
}
if r.channel == shutdown then
break
elseif r.channel == events then
handle_event(r.value)
else
handle_message(r.value)
end
end
```
### Non-Blocking Check
Use a default case to check for available data without blocking.
In this isolated pattern, `ch` and the `process` callback come from the application.
```lua
local r = channel.select {
ch:case_receive(),
default = true
}
if r.default then
-- Nothing available, do something else
elseif not r.ok then
-- The channel is closed
else
process(r.value)
end
```
## Creating Select Cases
Create send and receive cases for `channel.select`:
```lua
-- Send case - completes when channel can accept value
ch:case_send(value)
-- Receive case - completes when value available
ch:case_receive()
```
Values in the cases table that are not send or receive cases are ignored. Make sure the table contains at least one valid case unless it also has a default branch.
## Worker Pool Pattern
```lua
local items = {1, 2, 3, 4}
local num_workers = 2
local function process_item(item)
return item * 2
end
local work = channel.new(#items)
local results = channel.new(#items)
-- Spawn workers
for _ = 1, num_workers do
coroutine.spawn(function()
while true do
local item, ok = work:receive()
if not ok then
return
end
results:send(process_item(item))
end
end)
end
-- Feed work
for _, item in ipairs(items) do
work:send(item)
end
work:close()
-- Collect results
local processed = {}
while #processed < #items do
local result, ok = results:receive()
if not ok then break end
table.insert(processed, result)
end
```
After the loop, `processed` contains `2`, `4`, `6`, and `8`; result order depends on coroutine scheduling. The workers share channels because they are coroutines in the same Lua process.
## Errors
| Condition | Kind | Retryable |
|-----------|------|-----------|
| Send on closed channel | runtime error | no |
| `cases` argument to select is not a table | runtime error | no |
## See Also
- [Process Management](lua/core/process.md) - Process spawning and communication
- [Message Queue](lua/storage/queue.md) - Queue-based messaging
- [Functions](lua/core/funcs.md) - Function invocation
## Navigation
Previous: "Time & Duration" (lua/core/time)
Next: "Process Management" (lua/core/process)