Lua Coroutines (coroutine)
What is a Coroutine?
Lua coroutines are similar to threads: they have independent stacks, local variables, and instruction pointers, but they share global variables and other major aspects.
Coroutines are a powerful feature, but they can be complex to use.
Differences Between Threads and Coroutines
The main difference between threads and coroutines is that a multi-threaded program can run several threads simultaneously, while coroutines need to run cooperatively.
At any given moment, only one coroutine is running, and it will only be suspended when explicitly asked to do so.
Coroutines are somewhat similar to synchronous multi-threading, where several threads waiting for the same thread lock are akin to coroutines.
Basic Syntax
Method | Description |
---|---|
coroutine.create() | Creates a coroutine and returns it. The parameter is a function that is awakened when used with resume. |
coroutine.resume() | Resumes a coroutine, used in conjunction with create. |
coroutine.yield() | Suspends a coroutine, setting it to a suspended state. This is useful when used with resume. |
coroutine.status() | Checks the status of a coroutine. <br>Note: Coroutine statuses can be dead, suspended, or running. Refer to the program below for details. |
coroutine.wrap() | Creates a coroutine and returns a function. Calling this function enters the coroutine, duplicating the functionality of create. |
coroutine.running() | Returns the running coroutine, which is essentially a thread. This function returns the thread ID of the coroutine. |
Example Demonstrating the Usage of Various Methods:
coroutine_test.lua File
-- coroutine_test.lua file
co = coroutine.create(
function(i)
print(i);
end
)
coroutine.resume(co, 1) -- 1
print(coroutine.status(co)) -- dead
print("----------")
co = coroutine.wrap(
function(i)
print(i);
end
)
co(1)
print("----------")
co2 = coroutine.create(
function()
for i=1,10 do
print(i)
if i == 3 then
print(coroutine.status(co2)) --running
print(coroutine.running()) --thread:XXXXXX
end
coroutine.yield()
end
end
)
coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --3
print(coroutine.status(co2)) -- suspended
print(coroutine.running())
print("----------")
Output of the above example:
1
dead
----------
1
----------
1
2
3
running
thread: 0x7fb801c05868 false
suspended
thread: 0x7fb801c04c88 true
----------
From coroutine.running
, we can see that coroutines are implemented as threads at the lower level.
When creating a coroutine, it registers an event in a new thread.
When using resume
to trigger the event, the coroutine function created by create
is executed. When yield
is encountered, it suspends the current thread, waiting for the event to be triggered again by resume
.
Next, we analyze a more detailed example:
Example
function foo (a)
print("foo 函数输出", a)
return coroutine.yield(2 * a) -- Returns 2*a
end
co = coroutine.create(function (a , b)
print("第一次协同程序执行输出", a, b) -- co-body 1 10
local r = foo(a + 1)
print("第二次协同程序执行输出", r)
local r, s = coroutine.yield(a + b, a - b) -- a, b values are passed from the first call to the coroutine
print("第三次协同程序执行输出", r, s)
return b, "结束协同程序" -- b value is passed from the second call to the coroutine
end)
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割线----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割线---")
Output of the above example:
第一次协同程序执行输出 1 10
foo 函数输出 2
main true 4
--分割线----
第二次协同程序执行输出 r
main true 11 -9
---分割线---
第三次协同程序执行输出 x y
main true 10 结束协同程序
---分割线---
main false cannot resume dead coroutine
---分割线---
Explanation:
- Calling
resume
wakes up the coroutine;resume
returns true if successful, otherwise false. - The coroutine runs.
- It reaches the
yield
statement. yield
suspends the coroutine, and the firstresume
returns. (Note:yield
returns here, and the parameters are fromresume
.)- The second
resume
wakes up the coroutine again. (Note: The parameters ofresume
, except the first, are passed toyield
.) yield
returns.- The coroutine continues to run.
- If
resume
is called again after the coroutine completes, it outputs:cannot resume dead coroutine
.
The power of resume
and yield
lies in that resume
is in the main program, passing external state (data) into the coroutine; while yield
returns internal state (data) back to the main program.
Producer-Consumer Problem
Now, let's use Lua coroutines to solve the classic producer-consumer problem.
Example
local newProductor
function productor()
local i = 0
while true do
i = i + 1
send(i) -- Sends the produced item to the consumer
end
end
function consumer()
while true do
local i = receive() -- Receives the item from the producer
print(i)
end
end
function receive()
local status, value = coroutine.resume(newProductor)
return value
end
function send(x)
coroutine.yield(x) -- x is the value to be sent. The coroutine is suspended after the value is returned.
end
-- Start the program
newProductor = coroutine.create(productor)
consumer()
Output of the above example:
1
2
3
4
5
6
7
8
9
10
11
12
13
……