🎓 A1Learner
🔍
0 of 52 lessons completed

Lua Coroutine Scheduler

A coroutine scheduler is a small loop that resumes several coroutines in turn until all of them are dead. Each turn gives one coroutine a short run and then moves to the next.

Manual resumes show turns in order

Two coroutines print one step each and then yield. Four resumes run them in turn.

local a = coroutine.create(function()
  print("A1")
  coroutine.yield()
  print("A2")
end)
local b = coroutine.create(function()
  print("B1")
  coroutine.yield()
  print("B2")
end)
coroutine.resume(a)
coroutine.resume(b)
coroutine.resume(a)
coroutine.resume(b)

A loop can schedule every task until all are dead

The loop resumes each live task once per round. It stops when no task is left alive.

local t1 = coroutine.create(function()
  print("task1 step1")
  coroutine.yield()
  print("task1 step2")
end)
local t2 = coroutine.create(function()
  print("task2 step1")
  coroutine.yield()
  print("task2 step2")
end)
local tasks = {t1, t2}
local alive = true
while alive do
  alive = false
  for i = 1, #tasks do
    if coroutine.status(tasks[i]) ~= "dead" then
      alive = true
      coroutine.resume(tasks[i])
    end
  end
end

A scheduler can print values yielded on each turn

Two workers yield numbers on each turn. The scheduler prints each yielded number in turn order.

local w1 = coroutine.create(function()
  coroutine.yield(1)
  coroutine.yield(2)
end)
local w2 = coroutine.create(function()
  coroutine.yield(10)
  coroutine.yield(20)
end)
local tasks = {w1, w2}
local alive = true
while alive do
  alive = false
  for i = 1, #tasks do
    if coroutine.status(tasks[i]) ~= "dead" then
      alive = true
      local ok, value = coroutine.resume(tasks[i])
      if value then
        print(value)
      end
    end
  end
end

Example

local a = coroutine.create(function()
  print("A1")
  coroutine.yield()
  print("A2")
end)
local b = coroutine.create(function()
  print("B1")
  coroutine.yield()
  print("B2")
end)
local c = coroutine.create(function()
  print("C1")
  coroutine.yield()
  print("C2")
end)
local tasks = {a, b, c}
local turns = 0
local alive = true
while alive do
  alive = false
  for i = 1, #tasks do
    if coroutine.status(tasks[i]) ~= "dead" then
      alive = true
      turns = turns + 1
      coroutine.resume(tasks[i])
    end
  end
end
print(turns)

Key points

  • Scheduler: the scheduler is a loop that holds a list of coroutines and resumes them in turn.
  • Round robin: round robin means each coroutine gets one turn before any coroutine gets a second turn.
  • Turn: a turn is one resume of one coroutine.
  • Yield: yield ends a turn and gives control back to the scheduler.
  • Dead: the scheduler skips a coroutine once its status is dead.

In this example, the scheduler holds three tasks and resumes each live task once per round. Each task prints two steps across two turns. The scheduler stops after 6 turns when all three tasks are dead.

Note

a scheduler never runs two coroutines at once, so turns never overlap.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

When does a round robin scheduler stop?