Problem
As a Roblox developer, it is currently too hard to guarantee exclusive access to a resource. Since LUA is an interpreted language, there is no way to protect critical sections of code from interference when another thread needs to access the same resource when using LUA based access controls that are not atomic.
Use Cases
Consider the following code:
local totalSize = 1000000
local blockSize = 10000
local blockCount = math.floor(totalSize / blockSize)
local nextBlock = 0
local mutex = false
local threadCount
-- Acquires the lock for exclusive access.
local function lock()
while mutex == true do
task.wait()
end
mutex = true
end
-- Releases the lock.
local function unlock()
mutex = false
end
-- Performs the required task, calculating
-- the start and end block positions based
-- on the next block of items to process.
function doTask()
while nextBlock < blockCount do
-- Calculate start and end positions.
count = blockSize - 1
lock()
-- Start Critical Section
start = nextBlock * blockSize
nextBlock += 1
unlock()
-- End Critical Section
-- Perform Task
for i = start, count, 1 do
-- Perform your task here.
end
-- Breaks the execution up to prevent
-- script timeout errors.
task.wait()
end
end
for i = 1, threadCount, 1 do
task.spawn(function()
doTask()
-- Introduces a slight delay to help prevent a race
-- condition and to reduce lock contention.
task.wait()
end)
end
Within the function doTask()
, the critical section uses the value of nextBlock
twice. It is expected that the value does not change between accesses which is why I have it wrapped with lock()
and unlock()
. However, even with that, exclusivity is not guaranteed because the operation is not atomic. Because of this, the value of nextBlock
can be overwritten between statements and can lead to a race condition which is difficult to diagnose. The only true way to guarantee atomicity is to use the follow CPU instruction (Intel/AMD CPUs):
LOCK XCHGCMP [Address], Register
The above CPU instruction is fully atomic even in multi-threading/multi-processor environments because of the LOCK
prefix. As such, I suspect the LUA interpreter controls access to shared resources for a single LUA statement, but if multiple statements are required to access/update a value, then locks are needed in LUA code to designate critical sections for exclusive access while the LUA instruction pointer is in the critical section.
Conclusion
If Roblox addresses this issue, it would make it easier to designate a block of LUA code as being a critical section so exclusive access can be guaranteed. This will help to eliminate a source of issues relating to multi-threading such as race conditions which can be very difficult to pin down.
On a personal note, all I need is access to the spin lock primitives. From there, I can build semaphores, mutex locks, and read/write locks from that.