Lua coroutine



What is coroutine?

Lua coroutine is similar to a thread: it has an independent stack, independent local variables, and independent instruction pointer, and at the same time shares global variables and most other things with other coroutines.

Collaboration is a very powerful function, but it is also very complicated to use.

The difference between threads and coroutines

The main difference between threads and coroutines is that a program with multiple threads can run several threads at the same time, while coroutines need to run in cooperation with each other.

Only one coroutine is running at any given time, and the running coroutine will only be suspended when explicitly asked to suspend.

Coroutines are somewhat similar to synchronized multi-threads, and several threads waiting for the same thread lock are somewhat similar to collaboration.

Basic syntax

MethodDescription
coroutine.create()Create coroutine and return coroutine. The parameter is a function. When used with resume, the function call is awakened.
coroutine.resume()Restart Coroutine, used in conjunction with create
coroutine.yield()Suspend coroutine and set coroutine to suspended state. This can be used in conjunction with resume to be useful. Effect
coroutine.status()Check the status of coroutine
Note: There are three statuses of coroutine: dead, suspend, and running. When will it happen? For this status, please refer to the following program
coroutine.wrap()Create coroutine and return a function. Once you call this function, enter coroutine and create Function duplication
coroutine.running()Returns the running coroutine. A coroutine is a thread. When running is used, a corouting thread number is returned.

The following examples demonstrate the usage of each of the above methods:

-- coroutine_test.lua 文件
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 execution output of the above examples is:

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

It can be seen from coroutine.running that the underlying implementation of coroutine is a thread.

When creating a coroutine, an event is registered in a new thread.

When resume is used to trigger an event, the coroutine function of create is executed. When yield is encountered, it means suspending the current thread and waiting for resume to trigger the event again.

Next we analyze a more detailed example:

function foo (a)
    print("foo 函数输出", a)
    return coroutine.yield(2 * a) -- 返回  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的值为第一次调用协同程序时传入
     
    print("第三次协同程序执行输出", r, s)
    return b, "结束协同程序"                   -- b的值为第二次调用协同程序时传入
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("---分割线---")

The execution output of the above example is:

第一次协同程序执行输出	1	10
foo 函数输出	2
main	true	4
--分割线----
第二次协同程序执行输出	r
main	true	11	-9
---分割线---
第三次协同程序执行输出	x	y
main	true	10	结束协同程序
---分割线---
main	false	cannot resume dead coroutine
---分割线---

The above example continues as follows:

  • Call resume to wake up the coroutine. The resume operation returns true if successful, otherwise it returns false;

  • The coroutine runs;

  • Run to the yield statement;

  • yield suspends the coroutine, and the first resume returns; (Note: here yield returns, and the parameters are the parameters of resume)

  • Resume for the second time, wake up the coroutine again; (Note: Among the resume parameters here, except for the first parameter, the remaining parameters will be used as yield parameters)

  • yield returns;

  • The coroutine continues to run;

  • If the coroutine used continues to run and continues to call the resume method after completion, Output: cannot resume dead coroutine

The powerful combination of resume and yield is that resume is in the main process, and it passes external status (data) into the coroutine; and yield The internal status (data) is returned to the main process.


Producer-Consumer Problem

Now I use Lua's coroutine to complete the classic producer-consumer problem.

local newProductor

function productor()
     local i = 0
     while true do
          i = i + 1
          send(i)     -- 将生产的物品发送给消费者
     end
end

function consumer()
     while true do
          local i = receive()     -- 从生产者那里得到物品
          print(i)
     end
end

function receive()
     local status, value = coroutine.resume(newProductor)
     return value
end

function send(x)
     coroutine.yield(x)     -- x表示需要发送的值,值返回以后,就挂起该协同程序
end

-- 启动程序
newProductor = coroutine.create(productor)
consumer()

The execution output of the above example is:

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