Coroutines in Lua
Coroutines are one of Lua's most powerful features, enabling cooperative multitasking and sophisticated control flow. Unlike threads, coroutines are cooperativeβthey explicitly yield control to other coroutines. In this lesson, you'll learn how to create, manage, and use coroutines effectively. Let's explore this advanced feature!
What are Coroutines?
A coroutine is like a function that can pause and resume execution:
-- Create a coroutine
local co = coroutine.create(function()
print("Hello")
coroutine.yield()
print("World")
end)
-- Resume the coroutine
coroutine.resume(co) -- Prints: Hello
coroutine.resume(co) -- Prints: World
- Create:
coroutine.create(func)creates a coroutine - Resume:
coroutine.resume(co)starts/resumes execution - Yield:
coroutine.yield()pauses execution
Click Run to execute your code
Coroutine States
A coroutine can be in one of four states:
local co = coroutine.create(function()
print("Running")
coroutine.yield()
print("Done")
end)
print(coroutine.status(co)) -- suspended
coroutine.resume(co)
print(coroutine.status(co)) -- suspended (yielded)
coroutine.resume(co)
print(coroutine.status(co)) -- dead
| State | Description |
|---|---|
suspended |
Created but not started, or yielded |
running |
Currently executing |
normal |
Resumed another coroutine |
dead |
Finished execution or error |
Passing Values
Resume to Yield
local co = coroutine.create(function(a, b)
print("Received:", a, b)
local x, y = coroutine.yield(a + b)
print("Received after yield:", x, y)
return x * y
end)
local success, result = coroutine.resume(co, 10, 20)
print("First resume:", result) -- 30
local success, result = coroutine.resume(co, 5, 6)
print("Second resume:", result) -- 30
Yield to Resume
local co = coroutine.create(function()
for i = 1, 5 do
coroutine.yield(i)
end
end)
while coroutine.status(co) ~= "dead" do
local success, value = coroutine.resume(co)
if success and value then
print("Yielded:", value)
end
end
Click Run to execute your code
Coroutine Patterns
Producer-Consumer
local function producer()
return coroutine.create(function()
for i = 1, 10 do
coroutine.yield(i)
end
end)
end
local function consumer(prod)
while true do
local success, value = coroutine.resume(prod)
if not success or not value then
break
end
print("Consumed:", value)
end
end
local prod = producer()
consumer(prod)
Iterator with Coroutines
local function range(from, to)
return coroutine.wrap(function()
for i = from, to do
coroutine.yield(i)
end
end)
end
-- Use like a regular iterator
for i in range(1, 5) do
print(i) -- 1, 2, 3, 4, 5
end
coroutine.wrap() creates a coroutine and
returns
a function that resumes it. It's simpler than create() +
resume() for iterators.
Practical Examples
Task Scheduler
local Scheduler = {}
function Scheduler:new()
local self = {tasks = {}}
setmetatable(self, {__index = Scheduler})
return self
end
function Scheduler:add(func)
table.insert(self.tasks, coroutine.create(func))
end
function Scheduler:run()
while #self.tasks > 0 do
for i = #self.tasks, 1, -1 do
local task = self.tasks[i]
local success, err = coroutine.resume(task)
if not success then
print("Task error:", err)
table.remove(self.tasks, i)
elseif coroutine.status(task) == "dead" then
table.remove(self.tasks, i)
end
end
end
end
-- Usage
local scheduler = Scheduler:new()
scheduler:add(function()
for i = 1, 3 do
print("Task 1:", i)
coroutine.yield()
end
end)
scheduler:add(function()
for i = 1, 3 do
print("Task 2:", i)
coroutine.yield()
end
end)
scheduler:run()
Async File Reader
local function asyncReadFile(filename)
return coroutine.create(function()
local file = io.open(filename, "r")
if not file then
return nil, "File not found"
end
while true do
local line = file:read("*line")
if not line then break end
coroutine.yield(line)
end
file:close()
end)
end
-- Usage
local reader = asyncReadFile("data.txt")
while coroutine.status(reader) ~= "dead" do
local success, line = coroutine.resume(reader)
if success and line then
print("Line:", line)
end
end
State Machine
local function stateMachine()
return coroutine.create(function()
-- State 1: Idle
print("State: Idle")
local input = coroutine.yield()
-- State 2: Processing
if input == "start" then
print("State: Processing")
coroutine.yield()
-- State 3: Complete
print("State: Complete")
else
print("State: Error")
end
end)
end
local sm = stateMachine()
coroutine.resume(sm)
coroutine.resume(sm, "start")
coroutine.resume(sm)
Click Run to execute your code
coroutine.wrap() vs coroutine.create()
| Feature | create() | wrap() |
|---|---|---|
| Returns | Coroutine object | Function |
| Resume | coroutine.resume(co) | func() |
| Error handling | Returns success, result | Propagates errors |
| Use case | When you need error handling | Simple iterators |
Practice Exercise
Try these coroutine challenges:
Click Run to execute your code
Summary
In this lesson, you learned:
- What coroutines are and how they work
- Creating coroutines with
coroutine.create() - Resuming and yielding with
resume()andyield() - Coroutine states (suspended, running, normal, dead)
- Passing values between resume and yield
- Common patterns (producer-consumer, iterators)
- Practical examples (scheduler, async reader, state machine)
- Difference between
create()andwrap()
What's Next?
You've mastered coroutines! Next, we'll explore file I/Oβhow to read and write files, handle different file modes, and work with the file system. Let's continue! π
Enjoying these tutorials?