Concurrency#
Lua has no native threads. The language ships coroutines, a cooperative scheduling primitive that lets a function yield control and be resumed later. The runtime advances one coroutine at a time; there is no preemption and no shared-memory race.
For real parallelism the operator leans on the host. LuaJIT plus
lua-lanes runs Lua state per OS thread; OpenResty multiplexes
coroutines across nginx workers; cqueues is a single-thread
event loop on top of coroutines.
/types`. For network I/O that typically drives a coroutine event loop, see Networking.
Coroutines#
A coroutine is a function that can yield to pause itself and
return control to whoever called resume. The yielding side
hands values back; the resuming side hands values in.
local co = coroutine.create(function(a, b)
local x = coroutine.yield(a + b) -- pause, return a+b
local y = coroutine.yield(x * 2) -- pause, return x*2
return y + 1
end)
print(coroutine.resume(co, 1, 2)) --> true 3
print(coroutine.resume(co, 10)) --> true 20
print(coroutine.resume(co, 100)) --> true 101
coroutine.resume returns true, ...yields on yield or
true, ...returns on return; false, err on uncaught error
inside the coroutine.
Coroutine API#
Function |
Effect |
|---|---|
|
Wraps |
|
Starts or resumes |
|
Suspends the current coroutine, returning the values to
whoever called |
|
Returns a function that resumes |
|
|
|
Whether the current context can |
coroutine.wrap is the friendlier form when it does
not need the true / false discipline of resume.
local next_n = coroutine.wrap(function()
for i = 1, math.huge do coroutine.yield(i) end
end)
print(next_n(), next_n(), next_n()) --> 1 2 3
Coroutines as iterators#
Generic for accepts any function that returns the next value
or nil to stop. coroutine.wrap turns a yielding function
into exactly that.
local function range(a, b, step)
step = step or 1
return coroutine.wrap(function()
for i = a, b, step do coroutine.yield(i) end
end)
end
for i in range(1, 5) do print(i) end
Producer / consumer#
Coroutines are a natural fit for producer / consumer pipelines where each stage waits on the previous one.
local function producer()
for line in io.lines("input.log") do
coroutine.yield(line)
end
end
local function filter(prev)
for line in coroutine.wrap(prev) do
if line:find("error") then coroutine.yield(line) end
end
end
for line in coroutine.wrap(function() filter(producer) end) do
print(line)
end
cqueues#
cqueues is a single-thread event loop on top of coroutines. The operator writes blocking-style code; the loop schedules around I/O.
$ luarocks install cqueues
local cqueues = require("cqueues")
local socket = require("cqueues.socket")
local loop = cqueues.new()
loop:wrap(function()
local s = socket.connect("example.com", 80)
s:write("GET / HTTP/1.0\r\n\r\n")
for line in s:lines() do print(line) end
end)
loop:wrap(function()
for i = 1, 3 do
print("tick", i)
cqueues.sleep(1)
end
end)
loop:loop()
cqueues is the default for any single-host async
server work in Lua. The HTTP, DNS, and TLS surfaces ship as
companion libraries.
OpenResty light threads#
Inside OpenResty (nginx + LuaJIT) use
ngx.thread.spawn to fan out coroutines that share an nginx
worker. ngx.semaphore and ngx.shared.DICT cover the
coordination primitives.
local function fetch(url)
return ngx.location.capture(url)
end
local t1 = ngx.thread.spawn(fetch, "/api/a")
local t2 = ngx.thread.spawn(fetch, "/api/b")
local _, r1 = ngx.thread.wait(t1)
local _, r2 = ngx.thread.wait(t2)
lua-lanes#
For true preemptive parallelism on multiple cores, lua-lanes spawns a new Lua state per OS thread. State is isolated; data crosses lane boundaries through linda channels.
local lanes = require("lanes").configure()
local function work(n) return n * n end
local h = lanes.gen("*", work)(7)
print(h[1]) --> 49
Picking the model.
Need |
Reach for |
|---|---|
cooperative I/O in one process |
coroutines + |
long-lived servers behind nginx |
OpenResty ( |
CPU-bound parallel work |
|
References#
Types for the
threadtypecoroutine.createreturns.Functions for closures, the natural form of a coroutine body.
Networking for socket and HTTP work that typically drives the loop.