Coroutines
Syntax#
-
coroutine.create(function) returns a coroutine (type(coroutine) == ‘thread’) containing the function.
-
coroutine.resume(co, …) resume, or start the coroutine. Any additional arguments given to resume are returned from the coroutine.yield() that previously paused the coroutine. If the coroutine had not been started the additional arguments become the arguments of the function.
-
coroutine.yield(…) yields the currently running coroutine. Execution picks back up after the call to coroutine.resume() that started that coroutine. Any arguments given to yield are returned from the corresponding coroutine.resume() that started the coroutine.
-
coroutine.status(co) returns the status of the coroutine, which can be :
- “dead” : the function in the coroutine has reached it’s end and the coroutine cannot be resumed anymore
- “running” : the coroutine has been resumed and is running
- “normal” : the coroutine has resumed another coroutine
- “suspended” : the coroutine has yielded, and is waiting to be resumed
-
coroutine.wrap(function) returns a function that when called resumes the coroutine that would have been created by coroutine.create(function).
Remarks#
The coroutine system has been implemented in lua to emulate multithreading existing in other languages. It works by switching at extremely high speed between different functions so that the human user think they are executed at the same time.
Create and use a coroutine
All functions to interact with coroutines are avaliable in the coroutine table. A new coroutine is created by using the coroutine.create function with a single argument: a function with the code to be executed:
thread1 = coroutine.create(function()
print("honk")
end)
print(thread1)
-->> thread: 6b028b8c
A coroutine object returns value of type thread, representing a new coroutine. When a new coroutine is created, its initial state is suspended:
print(coroutine.status(thread1))
-->> suspended
To resume or start a coroutine, the function coroutine.resume is used, the first argument given is the thread object:
coroutine.resume(thread1)
-->> honk
Now the coroutine executes the code and terminates, changing its state to dead, wich cannot be resumed.
print(coroutine.status(thread1))
-->> dead
Coroutines can suspend its execution and resume it later thanks to the coroutine.yield function:
thread2 = coroutine.create(function()
for n = 1, 5 do
print("honk "..n)
coroutine.yield()
end
end)
As you can see, coroutine.yield() is present inside the for loop, now when we resume the coroutine, it will execute the code until it reachs a coroutine.yield:
coroutine.resume(thread2)
-->> honk 1
coroutine.resume(thread2)
-->> honk 2
After finishing the loop, the thread status becomes dead and cannot be resumed. Coroutines also allows the exchange between data:
thread3 = coroutine.create(function(complement)
print("honk "..complement)
coroutine.yield()
print("honk again "..complement)
end)
coroutine.resume(thread3, "stackoverflow")
-->> honk stackoverflow
If the coroutine is executed again with no extra arguments, the complement will still the argument from the first resume, in this case “stackoverflow”:
coroutine.resume(thread3)
-->> honk again stackoverflow
Finally, when a coroutine ends, any values returned by its function go to the corresponding resume:
thread4 = coroutine.create(function(a, b)
local c = a+b
coroutine.yield()
return c
end)
coroutine.resume(thread4, 1, 2)
print(coroutine.resume(thread4))
-->> true, 3
Coroutines are used in this function to pass values back to a calling thread from deep within a recursive call.
local function Combinations(l, r)
local ll = #l
r = r or ll
local sel = {}
local function rhelper(depth, last)
depth = depth or 1
last = last or 1
if depth > r then
coroutine.yield(sel)
else
for i = last, ll - (r - depth) do
sel[depth] = l[i]
rhelper(depth+1, i+1)
end
end
end
return coroutine.wrap(rhelper)
end
for v in Combinations({1, 2, 3}, 2) do
print("{"..table.concat(v, ", ").."}")
end
--> {1, 2}
--> {1, 3}
--> {2, 3}
Coroutines can also be used for lazy evaluation.
-- slices a generator 'c' taking every 'step'th output from the generator
-- starting at the 'start'th output to the 'stop'th output
function slice(c, start, step, stop)
local _
return coroutine.wrap(function()
for i = 1, start-1 do
_ = c()
end
for i = start, stop do
if (i - start) % step == 0 then
coroutine.yield(c())
else
_ = c()
end
end
end)
end
local alphabet = {}
for c = string.byte('a'), string.byte('z') do
alphabet[#alphabet+1] = string.char(c)
end
-- only yields combinations 100 through 102
-- requires evaluating the first 100 combinations, but not the next 5311633
local s = slice(Combinations(alphabet, 10), 100, 1, 102)
for i in s do
print(table.concat(i))
end
--> abcdefghpr
--> abcdefghps
--> abcdefghpt
Coroutines can be used for piping constructs as described in Programming In Lua. The author of PiL, Roberto Ierusalimschy, has also published a paper on using coroutines to implement more advanced and general flow control mechanics like continuations.