Easy Tutorial
❮ Lua Tables Lua Decision Making ❯

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:

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
……
❮ Lua Tables Lua Decision Making ❯