Stateful Goroutines in Lua
Our example demonstrates how to manage state using coroutines in Lua. This approach aligns with Lua’s cooperative multitasking model, where each piece of data is managed by a single coroutine.
local socket = require("socket")
-- In this example, our state will be owned by a single coroutine.
-- This ensures that the data is never corrupted with concurrent access.
-- To read or write that state, other coroutines will send messages
-- to the owning coroutine and receive corresponding replies.
-- These tables encapsulate the requests and provide a way for the
-- owning coroutine to respond.
local function readOp(key)
return {key = key, resp = coroutine.running()}
end
local function writeOp(key, val)
return {key = key, val = val, resp = coroutine.running()}
end
local function main()
local readOps, writeOps = 0, 0
-- The reads and writes channels will be used by
-- other coroutines to issue read and write requests.
local reads, writes = {}, {}
-- This coroutine owns the state, which is a table
-- private to this stateful coroutine.
local stateCoroutine = coroutine.create(function()
local state = {}
while true do
local op
if #reads > 0 then
op = table.remove(reads, 1)
coroutine.resume(op.resp, state[op.key])
elseif #writes > 0 then
op = table.remove(writes, 1)
state[op.key] = op.val
coroutine.resume(op.resp, true)
else
coroutine.yield()
end
end
end)
-- Start 100 coroutines to issue reads to the state-owning coroutine.
for r = 1, 100 do
coroutine.wrap(function()
while true do
local read = readOp(math.random(1, 5))
table.insert(reads, read)
coroutine.resume(stateCoroutine)
coroutine.yield()
readOps = readOps + 1
socket.sleep(0.001)
end
end)()
end
-- Start 10 coroutines for writes, using a similar approach.
for w = 1, 10 do
coroutine.wrap(function()
while true do
local write = writeOp(math.random(1, 5), math.random(1, 100))
table.insert(writes, write)
coroutine.resume(stateCoroutine)
coroutine.yield()
writeOps = writeOps + 1
socket.sleep(0.001)
end
end)()
end
-- Let the coroutines work for a second.
socket.sleep(1)
-- Finally, capture and report the op counts.
print("readOps:", readOps)
print("writeOps:", writeOps)
end
main()
Running our program shows that the coroutine-based state management example completes a number of total operations:
$ lua stateful_coroutines.lua
readOps: 71708
writeOps: 7177
For this particular case, the coroutine-based approach in Lua is quite different from the original goroutine-based one in Go. Lua uses cooperative multitasking with coroutines, while Go uses preemptive multitasking with goroutines. This Lua implementation simulates concurrent behavior using a main loop and yielding coroutines.
In Lua, we don’t have true parallelism, but we can achieve concurrent behavior through coroutines. This approach might be useful in certain cases, especially in single-threaded environments or when dealing with I/O operations.
You should use whichever approach feels most natural, especially with respect to understanding the correctness of your program. In Lua, coroutines are often used for managing complex control flow and implementing cooperative multitasking.