First, a digression:
- Lua does not have threads -- only one coroutine runs at a time
- Coroutines are way more powerful than running two pieces of code "at the same time"
Coroutines work by adding a "pause & play" mechanic to functions. It let's one function "return" many times. coroutine.resume is analogous to calling the function and coroutine.yield is analogous to returning a value.
fibs = coroutine.create(function()
local a, b = 0, 1
while true do
coroutine.yield(a)
a, b = b, a + b
end
end)
print( coroutine.resume(fibs) )
print( coroutine.resume(fibs) )
print( coroutine.resume(fibs) )
print( coroutine.resume(fibs) )
print( coroutine.resume(fibs) )
print( coroutine.resume(fibs) )
print( coroutine.resume(fibs) )
The truemeans the coroutine is still alive (the function hasn't actually returned).
The remaining values are what were yielded.
Notice that this seems very much like a function call!
In fact, you want that so often Lua provides coroutine.wrap which would make the above look like the following:
fibs = coroutine.wrap(function()
local a, b = 0, 1
while true do
coroutine.yield(a)
a, b = b, a + b
end
end)
print( fibs() )
print( fibs() )
print( fibs() )
print( fibs() )
print( fibs() )
print( fibs() )
print( fibs() )
Wait.. Didn't you say only one coroutine runs at a time?
Yes -- so how does spawning a new coroutine let you do two things at once?
The key is how wait works.
What actually happens when you wait is that the current coroutine yields. Since no thread is running at that point, ROBLOX decides which thread is supposed to work next (based on which has been waiting long enough, etc), and resumes that one.
Why does all this matter
As I said, coroutine.resume is like a function call. spawn is a little different. While ultimately coroutine.resume(coroutine.create(f)) and spawn(f) act very similarly, there's an important difference.
coroutine.resume will immediately invoke the function (because it needs to -- because it's supposed to be able to get a result out, it has to). spawn just records a new "thread" that ROBLOX will watch, and resume when it next gets a chance (when this thread yields, e.g., by waiting)
This means that spawn can have some amount of latency on its function beginning, but coroutine.resume won't.
However, this shouldn't be a problem. Again, since only one coroutine runs at a time, it shouldn't really make a difference which one you use. If you need to make something happen immediately, just do that before spawning the thread.
Once the coroutines are running, they're completely equal. Neither will do better than the other.
Microoptimization
"Premature optimization is the root of all evil" - Donal Knuth
If you were working with numbers and you were wondering,
Which is faster, x ^ 0.5 or math.sqrt(x)?
Don't.
Applying all of those optimizations makes your code much harder to deal with -- for people trying to help you and yourself.
If you are just making a new 'thread', it's probably wise to use spawn -- it shows precisely that you're creating a new thread and not using the power of coroutines.
Also see
delay is similar to spawn, but let's you say how far in the future you want the function to happen. delay(t, f) is like tacking a wait(t) to the beginning of f.
1 comment
Amazing explanation. You're explanation actually gave me more questions, then answered them along the way. This was very helpful, thank you.