If I coroutine.yield()
without ever coroutine.resume()
, should I be concerned about that yielded thread? As in will it ever get cleaned up or will it always consume memory so long as it is yielded?
Yeah, be careful with that if you don’t use coroutine.resume the script will repeat constantly and this causes lag in your game
That’s not how coroutine.yield works tho, once the thread is yielded, the script doesn’t repeat or anything, it just stays stagnant until resumed.
it does, but in another thread
No, no it doesn’t, once a thread is yielded it simply halts. Go read the docs here.
Oh sorry, my bad ‎ ‏‏‎‎ ‏‏‎‎ ‏‏‎‎ ‏‏‎‎ ‏‏‎
local routine = coroutine.create(function()
print("Hello world!")
coroutine.yield()
print("Hello world!")
end)
coroutine.resume(routine)
task.wait(5)
print(routine)
This does not answer the question because routine
stays in scope during the execution of the code.
Theoretically, the threads should get properly cleaned up as long as no references are held to them. This can be seen in this code block, which loads in 100 suspended threads into a weak table. When it comes time to print them out, most don’t even make it out of the numerical for loop
before getting garbage collected, and none remain after yielding for one second.
local test = setmetatable({},{__mode = "v"})
local function yielding_method()
print("STARTED.")
coroutine.yield()
print("RESUMED.")
end
for index = 1,100 do
local thread = coroutine.create(yielding_method)
coroutine.resume(thread)
table.insert(test,thread)
end
print("CURRENT THREADS:")
for _,thread in pairs(test) do
print(thread)
end
task.wait(1)
print("REMAINING THREADS:")
for _,thread in pairs(test) do
print(thread)
end
You should resume a coroutine’s execution until its completion, leaving them in a permanant state of suspension is unideal.
Wait, can you use this to make a game pause script?
This doesn’t show that they actually get gced, the threads could still remain internally even though our access to them were gced.
Why’s it unideal, if it truely gets cleaned, then we shouldn’t worry.
This code shows that the threads are being collected because the size of Lua in memory only temporarily increases, then drops back down:
local test = setmetatable({},{__mode = "v"})
task.wait()
local function yielding_method()
print("STARTED.")
coroutine.yield()
print("RESUMED.")
end
print("STARTING LUA SIZE:")
print(gcinfo())
for index = 1,1000 do
local thread = coroutine.create(yielding_method)
coroutine.resume(thread)
table.insert(test,thread)
end
print("CURRENT LUA SIZE:")
print(gcinfo())
task.wait(1)
print("REMAINING LUA SIZE:")
print(gcinfo())
If you replace test
with a normal table instead of a weak table, the references are held onto, the threads are kept in memory, and the size of Lua in memory remains constant rather than dropping back down.
Despite this being a seemingly unhelpful reply. I would like to ask what is the necessity for having a coroutine that you have no intentions to resume at some point. If this is a question regarding something you are working right now, might it be possible to refactor your current solution to something that isn’t reliant on a coroutine that may remain suspended indefinitely?
Weak tables aren’t really super representative of the answer here though- it forces these objects to be collected because you overwrite the strong references in your examples. Both the value inside the table and the original variable (at each specific iteration) point to the same memory chunk which means that if the table was not weak it wouldn’t be collected.
I think OP more means if he should be worried about leaving it around in general use-cases, which he should if the script is going to run for a while and the coroutine isn’t going to go out of scope. That memory will still be occupied by the coroutine.
Say for example I am fetching player data, I have a module script with a Get
function that lets me get the player’s data if it exists, but if it doesn’t it just yields until it exists. Now let’s say the player leaves, the data will never exist thus the thread will yield infinitely. I know I can just listen for the player leaving and just return nil if they leave. But that got me thinking if I should worry about a yielded thread to begin with.
So a yielded thread will still occupy memory? I mean it only makes sense but can you confirm it?
I do see your use case for this. However, I still don’t see the need to make use of an unresolved thread in this situation.
Something that may be beneficial to you in this case might be a queue-event based data management system that relies on a “queue” to hold the requests and processes it in the background. When the request is being handled by the system there are several events attached to it that get fired upon a data related “event”. This will guard from un-necessary resource usage elsewhere if implemented correctly.
Yes- every object you create takes up memory until it is garbage collected. That is how Lua works. With automatic memory management (aka garbage collection, which happens just before the simulation job each frame on Roblox) things become easier but you still need to understand where it falls short.
If you pause a coroutine and your script doesn’t end, Lua will not collect that coroutine unless all referances to it become weak or it goes out of scope. This is to say that if a coroutine is the first memory you create referance to in your code at the very top, it will not be collected until it is handled properly or the code finishes. It will be indefinitely considered a “black object” to the garbage collector, and will continue to exist each frame. Logically it is easy to understand that internally there’s no reason to collect it because you are likely to use it again.
local routine = coroutine.create(function() -- This variable now holds a strong referance to your coroutine.
coroutine.yield() -- Let's pretend we don't need it anymore.
end)
local clock = os.clock()
while os.clock() - clock < 3 do -- Simulate code running for 3 seconds.
task.wait()
print(routine) -- This thread still exists because the strong referance to it has not been disposed of.
end
Great resource: Understanding Lua's Garbage Collection