Lua Coroutines: Threads from Beyond

Coroutines in Lua offer a unique model of concurrency: cooperative multitasking. Unlike OS threads, coroutines yield control voluntarily, eliminating race conditions and the need for locks.

Creating and Resuming

local co = coroutine.create(function(x)
    print("First run:", x)
    local y = coroutine.yield(x * 2)
    print("Resumed with:", y)
    return x + y
end)

local ok, result = coroutine.resume(co, 10)  -- First run: 10
print(result)  -- 20

local ok, result = coroutine.resume(co, 5)   -- Resumed with: 5
print(result)  -- 15

Coroutine vs OS Threads

  • No preemption: A coroutine runs until it explicitly yields. No surprise context switches.
  • No locks needed: Since only one coroutine runs at a time, shared state access is naturally serialized.
  • Lightweight: Each coroutine costs a few kilobytes. You can run thousands simultaneously.
  • Deterministic: The execution order is entirely controlled by the programmer.

Async I/O Patterns

Coroutines shine in I/O-bound applications. Wrap blocking calls in a scheduler that yields on I/O and resumes when data is ready:

function async_read(socket)
    while not socket:has_data() do
        coroutine.yield()  -- Let other coroutines run
    end
    return socket:read()
end

The Power of Cooperative Concurrency

In a world obsessed with parallelism, coroutines remind us that concurrency and parallelism are different beasts. Sometimes, elegant cooperation beats brute-force threading.