ThreadRecycler
A thread pool module strictly written in Luau.
GitHub Repo | Documentation & Tutorial | Releases | Last updated: 2/6/25
v0.3.0 is released! Improvements, API changes, and more!
View benchmarks before and after the release of v0.3.0
Important change from v0.2.x; the module now works as it should now, recycling threads properly.
This gives a huge performance improvement, now that threads are being recycled and reused properly. However, I have added breaking API changes that may not be backwards compatible with v0.2.x. More changes are stated in GitHub releases.
This is based solely on calling the function. Both functions were called 10000 times.
Before (v0.2.2)
After (v0.3.0)
As you can see from this histogram, v0.3.0 performs way better than v0.2.x.
Additionally, I made a post about this module before (which is why I have this post labled as Take #2), but unlisted it due to the recent problem with v0.2.x.
[MOVED] ThreadRecycler: Recycle and reuse threads! | v0.2.2
Performance
ThreadRecycler optimizes thread usage by recycling and reusing it in a thread pool, which reduces resource consumption. Fewer resources are wasted on unnecessary threads leading to faster execution.
Limitations
- Must replace
task.wait()
withThreadPool.wait()
to prevent issues in the task library (more context in the documentation. However, if you only useThreadPool.wrap()
in a thread pool (& don’t use any other functions), this step might not be necessary. - Using
ThreadPool.unsafedefer()
andThreadPool.unsafedelay()
should be avoided due to task library issues- (Possible warning if used) “task.defer should not be called on a thread that is already ‘deferred’ in the task library”
-
May have type errors on the new type solver?
- Unfortunately, the beta feature is bugged on my side so I’m unsure whether there’s type errors or not.
- As this is my first open-source and public module, it may require significant improvements.
Benchmarks
(For Take #2, I have provided better benchmarks).
Figure 1: task.spawn vs. recycler.spawn
Histogram based on calls and how much time it took to run the function in ms. This was created using Benchmarker.
This is based solely on calling the function. Both functions were called 10000 times.
If you have the Benchmarker plugin, you can try this out yourself by going into the GitHub repo and downloading the bench file. (root/benchmarks)
Figure 2: coroutine.wrap vs recycler.wrap
Histogram based on calls and how much time it took to run the function in ms. This was created using Benchmarker.
This is based solely on calling the function. Both functions were called 10000 times.
Note
If you have the Benchmarker plugin, you can try this out yourself by going into the GitHub repo and downloading the bench file. (root/benchmarks)
Only spawn
and wrap
are compared, as using task.defer
is considered “unsafe” in this module. delay
cannot be benchmarked as there cannot be yielding.
Installation (via GitHub Releases)
Head over to releases and download the .rbxm
file from the latest version. Then, insert the .rbxm
file into Roblox Studio.
Require the module:
local ThreadRecycler = require(game:GetService("ReplicatedStorage").ThreadRecycler)
local ThreadPool = ThreadRecycler.erect({ -- erect basically means to build or create
["InitialThreadCount"] = 60, -- Initial thread count
["CachedLifetime"] = 60, -- Cached lifetime
["EnableStatRecording"] = true, -- Enables recording of stats
["Logger"] = warn, -- Log method; doesn't do anything right now. Coming in the future...
["Debug"] = false -- Debug configuration. Doesn't do anything right now. Coming in the future...
})
I strongly recommend that you use the documentation for more in-depth details.
License
ThreadRecycler is fully open-source and free to use with a MIT license.
Contributions
All contributions are welcome! :)
Thanks to:
@RailTypes for giving feedback and recommendations
@2jammers for recommending the Benchmarker plugin
Usage Example
local ThreadRecycler = require(game:GetService("ReplicatedStorage").ThreadRecycler)
local PoolConfig = {
["InitialThreadCount"] = 10,
["CachedLifetime"] = false,
["EnableStatRecording"] = false,
["Logger"] = warn,
["Debug"] = false
}
local CoroutineThreadPool = ThreadRecycler.erect(PoolConfig)
local TaskThreadPool = ThreadRecycler.erect(PoolConfig) -- it would be best to keep them both separated
local function test(thread, x)
print("Hello!")
task.wait(1) -- should not cause any issues since wrap isn't in the task library
print("Hello world after 1 second!")
-- do some stuff; lets say that it may yield forever or never actually 'end'.
CoroutineThreadPool.recycle(CoroutineThreadPool, thread) -- if so, then this would be needed to properly recycle the thread
end
TaskThreadPool.spawn(TaskThreadPool, function(thread)
print("Hello world!")
TaskThreadPool.wait(5) -- must be used instead of task.wait() to prevent task library issues
print("Hello again after 5 seconds!")
end)
CoroutineThreadPool.wrap(CoroutineThreadPool, test, 1)
--// Example of autocompatible
TaskThreadPool.autocompatible(TaskThreadPool, test, 1) -- -> ThreadRecycler: This function supports auto-recycling!
-- If that statment never shows up, then you must manually recycle.
(just realized fastspawn exists and works like this except with less features )