11.2.3 Counting semaphores in Lua
As we worked through our counting semaphores in chapter 6, we spent a lot of time
trying to ensure that our semaphores had some level of fairness. In the process, we
used a counter to create a sort of numeric identifier for the client, which was then
used to determine whether the client had been successful. But because we still had
race conditions when acquiring the semaphore, we ultimately still needed to use a
lock for the semaphore to function correctly.
Let’s look at our earlier implementation of the counting semaphore and think
about what it may take to improve it using Lua.
In the process of translating this function into Lua, after cleaning out timed-out semaphores,
it becomes possible to know whether a semaphore is available to acquire, so
we can simplify our code in the case where a semaphore isn’t available. Also, because
everything is occurring inside Redis, we don’t need the counter or the owner ZSET,
since the first client to execute their Lua function should be the one to get the semaphore.
The Lua version of acquire_semaphore() can be seen in the next listing.
This updated semaphore offers the same capabilities of the lock-wrapped
acquire_fair_semaphore_with_lock(), including being completely fair. Further,
because of the simplifications we’ve performed (no locks, no ZINTERSTORE, and no
ZREMRANGEBYRANK), our new semaphore will operate significantly faster than the previous
semaphore implementation, while at the same time reducing the complexity of
the semaphore itself.
Due to our simplification, releasing the semaphore can be done using the original
release_semaphore() code from section 6.3.1. We only need to create a Lua-based
refresh semaphore function to replace the fair semaphore version from section 6.3.3,
With acquire and refresh semaphore rewritten with Lua, we now have a completely
fair semaphore that’s faster and easier to understand.
Now that we’ve rewritten locks and semaphores in Lua and have seen a bit of what
they can do to improve performance, let’s try to remove WATCH/MULTI/EXEC transactions
and locks from two of our previous examples to see how well we can make them