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.