English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Lua Coroutines

What is a coroutine?

Lua coroutines (coroutines) are similar to threads: they have their own stack, local variables, and instruction pointer, while also sharing global variables and most other things with other coroutines.

Coroutines are a very powerful feature, but they are also quite complex to use.

Thread and coroutine differences

The main difference between threads and coroutines is that a program with multiple threads can run multiple threads simultaneously, while coroutines need to run in collaboration with each other.

At any given moment, only one coroutine is running, and the running coroutine can only be suspended when explicitly requested.

Coroutines are somewhat similar to synchronized multithreading, and threads waiting for the same thread lock are somewhat similar to coroutines.

Basic syntax

MethodDescription
coroutine.create()Create a coroutine, returns coroutine, the parameter is a function, when used with resume, it wakes up the function call
coroutine.resume()Restart coroutine, used with create
coroutine.yield()Suspend coroutine, set the coroutine to suspended state, this can be used with resume to achieve many useful effects
coroutine.status()Check the status of coroutine
Note: The status of coroutine has three: dead, suspended, running, specific when such status occurs, please refer to the following program
coroutine.wrap()Create a coroutine, returns a function, once you call this function, you enter the coroutine, which is redundant with the create function
coroutine.running()Returns the running coroutine, a coroutine is a thread, when using running, it returns the thread number of the coroutine

The following examples demonstrate the usage of the above methods:

-- 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("----------")

The output result of the above example is:

1
dead
----------
1
----------
1
2
3
running
thread: 0x7fb801c05868    false
suspended
thread: 0x7fb801c04c88    true
----------

coroutine.running can be seen, coroutine is implemented as a thread in the underlying implementation.

When creating a coroutine, it is equivalent to registering an event in a new thread.

When using resume to trigger an event, the coroutine function of create is executed, and when encountering yield, it means to suspend the current thread and wait for the next resume to trigger the event.

Next, we analyze a more detailed example:

function foo (a)
    print("foo function output", a)
    return coroutine.yield(2 * a) -- Return  2*The value of a
end
 
co = coroutine.create(function (a , b)
    print("The first cooperative program execution output", a, b) -- co-body 1 10
    local r = foo(a + 1)
     
    print("The second cooperative program execution output", r)
    local r, s = coroutine.yield(a + b, a - b)  -- The values of a, b are the values passed when the cooperative program is called for the first time
     
    print("The third cooperative program execution output", r, s)
    return b, "End the cooperative program"                   -- The value of b is the value passed when the cooperative program is called for the second time
end)
        
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--Separator line----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---Separator line---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---Separator line---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---Separator line---")

The output result of the above example is:

The first cooperative program execution output    1    10
Output of the foo function    2
main    true    4
--Separator line----
The second cooperative program execution output    r
main    true    11    -9
---Separator line---
The third cooperative program execution output    x    y
main    true    10    End the cooperative program
---Separator line---
main    false    cannot resume dead coroutine
---Separator line---

The following example continues:

  • Call resume to wake up the cooperative program, resume operation returns true if successful, otherwise returns false;

  • The cooperative program runs;

  • Run to the yield statement;

  • Yield the cooperative program, the first resume returns; (Note: The yield returns, the parameter is the resume parameter)

  • Second resume, wake up the coroutine again; (Note: here, the parameters in the resume parameter, except the first parameter, the remaining parameters will be as the parameters of yield)

  • yield returns;

  • Coroutine continues to run;

  • If the coroutine continues to run after the resume method is called after it is completed, it will output: cannot resume dead coroutine

The powerful combination of resume and yield lies in the fact that resume is in the main program, it passes the external state (data) into the coroutine; while yield returns the internal state (data) back to the main program.

Producer-Consumer Problem

Now I will use Lua's coroutines to complete the producer-This is a classic consumer problem.

local newProductor
function productor()
     local i = 0
     while true do
          i = i + 1
          send(i)     -- Send the produced item to the consumer
     end
end
function consumer()
     while true do
          local i = receive()     -- Get 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 represents the value to be sent, and the coroutine is suspended after the value is returned
end
-- Start the program
newProductor = coroutine.create(productor)
consumer()

The output result of the above example is:

1
2
3
4
5
6
7
8
9
10
11
12
13
...