Mutexes in Lua

In the previous example we saw how to manage simple counter state using atomic operations. For more complex state we can use a mutex to safely access data across multiple threads.

local lanes = require("lanes")

-- Container holds a table of counters; since we want to
-- update it concurrently from multiple threads, we
-- add a mutex to synchronize access.
local Container = {}
Container.__index = Container

function Container.new()
    return setmetatable({
        mutex = lanes.mutex(),
        counters = {a = 0, b = 0}
    }, Container)
end

-- Lock the mutex before accessing counters; unlock
-- it at the end of the function.
function Container:inc(name)
    self.mutex:lock()
    self.counters[name] = self.counters[name] + 1
    self.mutex:unlock()
end

-- This function increments a named counter
-- in a loop.
local function doIncrement(container, name, n)
    for i = 1, n do
        container:inc(name)
    end
end

local function main()
    local c = Container.new()

    -- Run several threads concurrently; note
    -- that they all access the same Container,
    -- and two of them access the same counter.
    local threads = {
        lanes.gen("*", doIncrement)(c, "a", 10000),
        lanes.gen("*", doIncrement)(c, "a", 10000),
        lanes.gen("*", doIncrement)(c, "b", 10000)
    }

    -- Wait for the threads to finish
    for _, thread in ipairs(threads) do
        thread:join()
    end

    print(string.format("a: %d, b: %d", c.counters.a, c.counters.b))
end

main()

Running the program shows that the counters updated as expected.

$ lua mutexes.lua
a: 20000, b: 10000

In this Lua implementation, we use the lanes library to provide multi-threading capabilities and mutex synchronization. The Container class is implemented as a Lua table with methods. The mutex is created using lanes.mutex().

The doIncrement function is defined locally and then passed to lanes.gen() to create new threads. We use a table to keep track of the threads and join them at the end to wait for their completion.

Note that Lua doesn’t have built-in support for concurrency, so we rely on the lanes library to provide this functionality. This approach may differ from the original Go implementation in some details, but it achieves the same goal of safely managing shared state across multiple threads.

Next we’ll look at implementing this same state management task using only coroutines and channels.