Scripting Helpers is winding down operations and is now read-only. More info→
Ad
Log in to vote
3

What is the Purpose of Threads?

Asked by
Pyracel 55
6 years ago

Can someone elaborate as to when or why I might need a to make a thread like this?

local newThread = coroutine.create(function()

end)

2 answers

Log in to vote
4
Answered by
Link150 1355 Badge of Merit Moderation Voter
6 years ago

You rarely, if ever, want to use coroutines directly in Roblox.

The Roblox API features two very useful functions, being spawn(f) and delay(t, f), designed to help you write multi-threaded code easier.

spawn and delay internally make use of coroutines, however these coroutines are all managed by Roblox's scheduler, so that you don't have to worry about the little details.

spawn(f) takes a function f and adds it to the task scheduler's queue, so that the task is executed next after the current thread yields.

delay(t, f) works similarly to spawn, except it will put the thread into a sleep state upon creation. This sleep state will last at least t seconds, during which Roblox will not execute it. It works effectively the same as spawn(function() wait(t) -- [...] end).

Using Coroutines Directly

I've never had any real use for coroutines in Roblox, but understanding the way they work remains very important if you intend to use spawn and delay, so I'll explain them quickly.

A coroutine can be in one of several state. "running", "normal", "suspended", and "dead".

  • A "running" coroutine is one whose code is currently being executed.
  • A "normal" coroutine is one whose code was being executed and then resumed another coroutine. A "normal" coroutine cannot be resumed (as it is considered as already running).
  • A "suspended" coroutine is one that was paused by calling coroutine.yield().
  • A "dead" coroutine is a coroutine that is finished running. It has accomplished its task or ran into an error. A dead coroutine cannot be resumed (doing so will result in an error).

The first step to use a coroutine is to create it. This is done using coroutine.create(func), where func is a function to be used as the coroutine's "body". The coroutine will die once it reaches the end of that body function. At this point, the coroutine is in a suspended state.

The next step is to start the coroutine. This is done using coroutine.resume(co). What this will do is jump into the coroutine's body function. The first time the coroutine is resumed, execution will jump to the first line of the body function, like a regular function call. Resuming a coroutine will make its state go from "suspended" to "running".

Now, and this is why coroutines are powerful, a coroutine can be paused in the middle of its execution and then resumed again. I like to think of them as "pausable functions". Pausing in the middle of a function's execution is the whole point behind coroutines. You do so by calling coroutine.yield() from within the coroutine's body. When a coroutine yields, it will jump back to the code that resumed it, as if the last coroutine.resume() had returned. Similarly, the next time the coroutine is resumed, it will continue as if the coroutine.yield() returned. Yielding from a coroutine will make its state go from "running" to "suspended".

This way you can have several different functions running seemingly in parallel. I say "seemingly", because it is all an illusion. In truth, Lua can only ever execute a single function at any given time, as it is single-threaded. But of course, the C++ side of Roblox can make use of true parallel threads.

Conclusion

The difference between user-managed coroutines and coroutines created via Roblox's spawn and delay functions is that the former have to be resumed and manipulated manually, which can be a pain in the butt.

The thread scheduler is responsible for selecting and resuming threads with a higher priority first and listen for special requests from them. For example, did you know that all wait(t) does is yield the current thread and ask Roblox's scheduler to not resume the thread for at least t seconds?

Without Roblox's scheduler it would not be possible to wait. At least not without writing busy loops, which waste CPU time and max out CPU usage for no reason. Roblox's wait() function will instead ask the scheduler to pause the current thread, and then select and resume other threads that can be resumed in the meantime. If there are no other tasks to resume (i.e, all are waiting), then Roblox will return control to the OS so that it can continue executing other programs.

That's all I had to say about coroutines. I hope you got all of that. :) But honestly, just stick to spawn and delay, they will spare you headaches.

Ad
Log in to vote
0
Answered by 6 years ago

As a note, I will use coroutine.wrap to make my code short.

You can use these to run two pieces code at the same time, ex:

coroutine.wrap(function()
    wait(10)
    print("this will print last")
end)()
coroutine.wrap(function()
    wait(5)
    print("this will print first")
end)()

But this isn't all you can do. You can do

local function foo()
    local t = {}
    coroutine.wrap(function()
        wait(10)
        t[1] = "hello"
    end)()
    return t
end

and have code running after you return something!

You can also pause coroutines, like

local c = coroutine.create(function()
    print("hello! you called me one time")
    coroutine.yield()
    print("stop calling me!")
end)
coroutine.resume(c)
wait(5)
coroutine.resume(c)

See this for more information.

Hope this helps!

0
Actually, neither will print. The only `print()` calls that would be executed are the ones in your last example. Link150 1355 — 6y
0
Hmmm, Isn't it usually recommended to use spawn() or delay() instead of coroutine? Zafirua 1348 — 6y

Answer this question