Quick explanation on how threading works

Threads. The thing your computer runs on. If you’re on windows you have tens of thousands of threads running at once. They’re the whole reason why your computer (no matter how powerful) doesn’t run super slow.

Games like RimWorld are on a single threaded system meaning it can become laggy and slow as there’s so much to do on one thread.

Threads are like strings. Each string runs its own separate section of code which can help dramatically with performance. Each code block can be visualised like a bead, the more beads the heavier the string becomes and the heavier it is the slower it becomes and harder it becomes to do things.

Since scripts run top down any loops (for, while, repeat etc) will prevent scripts from running code below it until that loop is broken:

print(1)

while task.wait(0.25)
   print(2)
end

-- this won't run
print(3)

No matter the code you put below and no matter what you try to do nothing will run below until that loop is broken.


How do we run loops and prevent them from stopping full execution of our code?

We can do this a few ways:

  • Move the loop into a separate script (don’t really recommend)
  • Use task.spawn() or coroutines
  • Use other things for loops like RunService
  • Put the loops at the bottom of the code

For those who might get this stupid idea that you can run multiple loops at once at the bottom of your script without spawning a new thread/threading it won’t work.

Docs are at the top of your screen if you need help.

TDLR: Use more than one thread for running loops. increases performance and other things

4 Likes

There’s a huge misconception here, a Luau “thread” is not the same as a CPU thread. If you truly want multi-threaded functionality in Roblox, you should be looking at Parallel Luau and Actors.

Luau (and many other languages) work off the concept of “tasks”, when you create/spawn a Luau “thread” what you are really doing is creating another task that will be executed. If you have code in Task A (the main task for your script) and within that code you have task.spawn it will create a Task B which executes immediately. This is why the code after that task.spawn still runs even if it yields/errors for example.

Here is an image of how Roblox’s Task Scheduler works from their Docs site:

All of this is ran IN ORDER, so if you have code that blocks execution, this will affect everything else down the line as well. EDIT: Just to clarify, blocking execution does NOT mean things that yield the “thread”/task; what I specifically mean are expensive operations that take time to process, Luau is single threaded unless you are using Parallel Luau, so if a task takes time to execute it will hold up every other task after it. If you “yield” a task, it is simply ignored in the task execution order until it is resumed again.

task.defer works the same way except that the tasks you create using that function are ran AFTER all the main immediate tasks. Which is why code after a task.defer call will run immediately compared to using task.spawn which will make the code after it wait until it’s done executing.

So there is no “magical” way to improve performance by using these methods, if your code is slow then it will still be slow. You could move it to work on another CPU thread using Parallel Lua and now your slow code won’t affect the main thread, but it’ll just be slowing down another one.

So for example:

-- Roblox Services
local Run = game:GetService("RunService")

-- This runs on the Scripts task
print("A")

-- Creates a new task which gets immediately executed
task.spawn(
	function()
		-- Expensive operation which blocks the entire task scheduler
		local test = 0
		for index = 1, 1000000000 do
			test = math.sqrt(index)
		end
		print("B")
	end
)

-- Will be executed once the Stepped signal fires (look at Task Scheduler image)
Run.Stepped:Once(
	function()
		print("C")
	end
)

-- Creates a new task that is immediately executed after print("B")
task.spawn(
	function()
		print("D")
	end
)

-- Finally, this is printed once all our previous spawn tasks finish executing
print("E")

this will output:

  1. A
  2. B
  3. D
  4. E
  5. C

Why does it do that? Well let’s process this:

    1. This print is ran prior to any other code, so this is a given.
  • 2 & 3. We create a task which executes a for loop and our print("B") statement, print("D") executes right after it because all active tasks are ran together during this process, it’s like a Queue
    1. Now that our other tasks that we immediately spawned have finished executing we can finally continue on with the rest of the main task which ends with print("E")
    1. Finally, now that all the tasks have been processed, we can continue on with the rest of the Schedule which happens to contain the “Stepped” signal firing, which our code for that is print("C")
7 Likes

You are right and I was a little wrong but this isn’t supposed to be in-depth really and Is here mainly to help those whom don’t know threads to help them visualise and somewhat understand why x won’t work and a couple ways to resolve such. Appreciate the reply though.

Right but you weren’t just a “little” wrong you were majorly wrong. You basically described multi-threading rather than single threaded task scheduling; what your post does is implies that all of your listed methods are ran concurrently when they are NOT. It also implies that using these methods will improve code performance (which they do NOT)!

There is no problem in making a post that simplifies a concept for people to understand, but there is a GIANT problem with explaining things that imply a completely different concept than the one you are attempting to explain. It’s misleading and dangerous because people who may not know better are now going to walk away from your post with incorrect knowledge of how these things work.

3 Likes