Basics Lua learning nine <Lua coroutines (Coroutine)>

introduction

Cheng talked about the Association, the first to introduce the difference between threads and coroutines

lua coroutine and multithreading

In common: they have their own independent top of stack, local variables and PC counter, while sharing global variables, and most other things with other coroutine

The difference: a multi-threaded program can run simultaneously several threads (concurrent execution, seize), and the process it takes to run the Association in cooperation with each other, not a true multi-threaded, that is, more than one coroutine program can only be run at the same time a coroutine, and it is performed only when it coroutine explicitly required suspended (suspend), its execution will be suspended (no preemption, uncomplicated).

note:

  • Coroutine in Lua can not stop it on the outside, it may lead to blocking program

  • Running is to call coroutine.yield () when the main thread being given LuaException: attempt to yield from outside a coroutine

Correlation function coroutine function

method description return value
coroutine.create (f) Create a principal function f of new coroutine. f must be a Lua function of. Returns this new coroutine, it is a type of "thread" object, and resume when you wake up with the use of function calls Returns its control (an object for the thread) object
coroutine.resume (co [, val1, ···]) From the co-operation of the pending changes to (1) activating coroutine, that is, let coroutine function starts running;. (2) wake yield, the co-pending then the last place to continue to run. This function can pass parameters A cyiled the incoming call parameters before running successfully and returns true; otherwise false + error message
coroutine.yield (···) Suspend execution of the calling coroutine. Passed to yieldthe parameter will be converted to resumean additional return value. Return to resume the argument passed
coroutine.status (co) Return coroutine co, as a string state: When coroutine is running (it is the status of the call), returns "running"; if the coroutine suspends or call the yield has not started running, return to "suspended"; if coroutine is active, are not running (that is, it is a continuation of another coroutine), return to "normal"; if the coroutine runs out of body function or stop because of an error, return to "dead". running,suspended,normal,dead
coroutine.wrap (f) Create a principal function f of new coroutine. f must be a Lua function of. Returns a function, each call to the function will be a continuation of the coroutine. The parameters are passed to this function as a resume of additional parameters. Resume and return the same value, but not the first Boolean. If any errors occur, this error is thrown. .
coroutine.running() Returns the currently running coroutine add a Boolean. If you are running coroutine is the main thread, it is true.
coroutine.isyieldable () If you are running coroutine can make out, it returns true. Not in the main thread or not can not be let out of a C function, the current co-routines is to get out of.

The following examples will be described simply created with coroutine is first create, look application example to understand the return parameters

print("coroutine start!");
 
--没有yield的协程
local newCor1 = coroutine.create(function()
    return 1,"a"
end)
 
local ret1, num1, str1 = coroutine.resume(newCor1)
 
print("1-----", ret1, num1, str1)
 
--包含一个yield的协程,主要看参数相关
local newCor2 = coroutine.create(function(x)
    x = x+10;
    --str和y的值为resume传入的值
    local str, y = coroutine.yield(x);
    return str, y + x
end)
 
local ret2, x = coroutine.resume(newCor2, 50)
print("2-----", x)
local ret3, str2, y = coroutine.resume(newCor2, "sss", 100);
print("2-----", str2, y)
--输出如下:
coroutine start!
1-----  true    1   a
2-----  60
2-----  sss 160

Then wrap can see the way, except that the specific, the return value is a wrap function, it need not arouse when invoked coroutine resume method, direct call function returned to wrap. There is a return and resume the difference is that the return value in less Boolean values ​​coroutine successful operation.

local newCor3 = coroutine.wrap(function(x)
    x = x - 10;
    local y = coroutine.yield(x);
    return y;
end)
 
--不需要resume函数来唤起,直接调用wrap返回的值
local ret4 = newCor3(100);
print("3-----", ret4)
local ret5 = newCor3(10);
print("3-----", ret5)
3-----  90
3-----  10

Continue taste usage.


co = coroutine.create(
    function(i)
        print(i);
    end
)
 
print(coroutine.status(co)) --suspended
coroutine.resume(co, 1)   -- 1
print(coroutine.status(co))  -- dead
coroutine.resume(co, 1) 
 
print("----------")
print(coroutine.status(co)) --dead
print(coroutine.resume(co)) --false cannot resume dead coroutine
 
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() --这里被挂起,也就是说for循环只会进行一次
        end
    end
)
 
coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --3
 
print(coroutine.status(co2))   -- suspended
print(coroutine.running())
 
print("----------")
suspended
1
dead
----------
1
----------
1
2
3
running
thread: 0x7f9c12c07c78  false
suspended
thread: 0x7f9c13001008  true
----------

coroutine.running can be seen, coroutine implementation is a thread at the bottom.

When create a coroutine it is to register an event in the new thread.

When using resume triggering event, create a coroutine function is executed, and when faced with the yield on behalf suspends the current thread, waiting again resume triggering event.

function foo (a)
    print("foo 函数输出", a)
    print("执行次数", 1,os.date())
    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("---分割线---")
第一次协同程序执行输出 1   10
foo 函数输出    2
执行次数    1   Tue Nov  5 13:23:41 2019
main    true    4
--分割线----
第二次协同程序执行输出 r
main    true    11  -9
---分割线---
第三次协同程序执行输出 x   y
main    true    10  结束协同程序
---分割线---
main    false   cannot resume dead coroutine
---分割线---

to sum up:

  • Call resume, will coordinate the program wakes up, returns true resume the operation was successful, otherwise it returns false;
  • Collaborative program run, run to the yield statement;
  • coroutine yield suspend, resume the first return; (Note: This return yield, resume the parameter is a parameter)
  • Second resume, again awakened cooperative programs; (Note: The parameters here resume, in addition to the first argument, the remaining parameters as yield parameters)
  • yield returns;
  • Collaborative program continues to run;
  • If you continue to run collaborative program uses methods continue to call the resume after the completion of the output: can not resume dead coroutine

And resume power of the yield is complex in that, in the main resume process will external state (data) is passed to the internal coordination program; will yield the interior of the state (data) is returned to the main process.

Overall, it looks a bit like a breakpoint and then executed. yield function may be running coroutine suspends, and can be re-awakened at the right time, and then continue to run, which is the essence of coroutines .

Coroutine execution Uniqueness

When a coroutine A resume when another coroutine B, A state does not become suspended, we can not go to resume it; but it is not running the state, because it is currently running a B. A state at this time is actually the normal state.

As already mentioned the return value case, then listed separately below deepen understanding

  1. the yield is not the main function, when calling resume, extra parameters are passed as parameters to the main function, the following example, 123 is the value of each of the abc:
co = coroutine.create(function (a,b,c)
print(a,b,c)
end
)
coroutine.resume(co,1,2,3)
1   2   3
  1. yield has the main function, all the parameters passed to the yield, are returned. Thus resume the return value, in addition to the correct operation flag true , there is passed the parameter values to yield:
co = coroutine.create(function(a,b)
coroutine.yield(a+b,a-b)end)
a,b,c = coroutine.resume(co,10,20)
print(a,b,c)
true    30  -10
  1. the extra parameters will yield to the corresponding return resume, as follows:
co = coroutine.create(function()
    print("co",coroutine.yield())
    end)
    print(coroutine.status(co))
    coroutine.resume(co)
    print(coroutine.status(co))
    print(coroutine.resume(co,4,5))
suspended
suspended
co  4   5
true

But there is a phenomenon, resume first return turned out to be empty? Not to implement, is not it strange? Look at the example:

coroutineFunc = function (a, b) 
    for i = 1, 10 do
        print(i, a, b)
        coroutine.yield()
    end
end

co2 = coroutine.create(coroutineFunc)        --创建协同程序co2
coroutine.resume(co2, 100, 200)                -- 1 100 200 开启协同,传入参数用于初始化
coroutine.resume(co2)                        -- 2 100 200 
coroutine.resume(co2, 500, 600)                -- 3 100 200 继续协同,传入参数无效

co3 = coroutine.create(coroutineFunc)        --创建协同程序co3
coroutine.resume(co3, 300, 400)                -- 1 300 400 开启协同,传入参数用于初始化
coroutine.resume(co3)                        -- 2 300 400 
coroutine.resume(co3)                        -- 3 300 400

The co-parameter passing situation is very flexible, must pay attention to distinguish, at the start coroutine time, resume parameters are passed to the main program; wake yield when the parameter is passed to the yield. Look at the following example:

co = coroutine.create(function (a, b) print("co", a, b, coroutine.yield()) end)
coroutine.resume(co, 1, 2)        --没输出结果,注意两个数字参数是传递给函数的
coroutine.resume(co, 3, 4, 5)        --co 1 2 3 4 5,这里的两个数字参数由resume传递给yield 

resume-yield data exchange:

(1) resume the parameters passed to the program (corresponding to the arguments to the call);

  (2) data is passed to yield the Resume;

  Parameter (3) resume transmitted to the yield;

  (4) at the end of the return value of the code synergistic also passed to resume

Nonsense step further, for example to explain the role of the above, let you pass a hundred percent perfect logic to understand,

coroutineFunc = function (a, b) 
    key  = a 
    print ("co",a)
    coroutine.yield(b)       
end

co2 = coroutine.create(coroutineFunc)      
print(coroutine.resume(co2, 100, 200))   
--co    100
--true  200

We all know the value returned resume; part is the result of collaboration itself return, the other part of the implementation of successful return true + value (this value is passed in yield), this is not straightened.

  1. When a coroutine end, all the main function return values ​​are returned to the resume:

  2. co = coroutine.create(function()
    return 6,7 end)
    print (coroutine.resume(co))
    true 6   7

to sum up

We are in the same coroutine, few of these features described above will all spend, but all of these functions are very useful.

So far, we have learned some knowledge of the coroutine in Lua. Now we need to clear a few concepts. Lua provides is asymmetric coroutine, meaning that it takes a function (yield) to suspend a coroutine, but need another function (resume) to awaken the suspended coroutine. Corresponding to the number of languages ​​provided Symmetric coroutine, for switching the function of the current only a coroutine.

Some people want the Lua coroutine called semi-coroutine, but the word has been used as another sense, it is used to represent a restriction of some functions to achieve out of the coroutine, such coroutine, only the call stack in a coroutine in when no residual any pending calls, will be suspended, in other words, only the main be suspended. The generator like Python is such a similar semi-coroutine.

With the difference between asymmetric coroutine and symmetric coroutine different, different (Python in) that the coroutine and generator, generator and it has a powerful coroutine, with a number of interesting features coroutine can be achieved with a generator can not be achieved. Lua provides a fully functional coroutine, if someone likes symmetric coroutine, you can own conduct some simple package.

pipes和filters

The following example is a good resume and yield practical applications.

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

function send (x)
    coroutine.yield(x)
end

function producer()
    return coroutine.create(function ()
        while true do
            local x = io.read() -- produce new value
                print("我进来了吗?1")
            send(x)
               print("我进来了吗?2")
        end
    end)
end
    
function consumer (prod)
    while true do
        local x = receive(prod) -- receive from producer
        io.write(x, "\n") -- consume new value
    end
end

p = producer()
consumer(p)

In short order of execution of this program first calls the consumer, then recv function to resume wake producer, a value produce, send to the consumer, and then continue to wait for the next wake-up time resume.

Also following the above example is split, to help understand the process.

function send(x) 
    coroutine.yield(x)
end

co = coroutine.create(
function()
    while true do
    local x = io.read()
    send(x) end
    end)
print(coroutine.status(co))
print(coroutine.resume(co))

We can continue to expand at above example, add a filter, do some data conversion between the producer and the consumer about her. So what do we do filter in it? We look at the logic did not increase before the filter, is the basic producer to send, send to consumer, consumer to recv, recv from producer, so to understand it. After adding a filter, because the filter needs to do some data conversion operation, so this time the logic, producer to send, send tofilter, filter to recv, recv from producer, filter to send, send to consumer, consumer to recv, recv from filter.


function send(x)
    coroutine.yield(x)
end

function receive (prod)
    local status, value = coroutine.resume(prod)
    print("echo 1")
    print("value is",value)
    return value
end

 
function producer()
    return coroutine.create(function ()
        print("echo 3")
        while true do
            local x = io.read()
            send(x)
        end 
    end)
end
 
function consumer(prod)
    
    while true do
        print("why ?")
        local x = receive(prod)
        if x then
            print("echo 2")
            io.write(x, '\n')
        else
            break
        end 
    end 
end
 
function filter(prod)                                                                                                              
    return coroutine.create(function ()
        for line = 1, math.huge do
            print("echo ")
            local x = receive(prod)
            x = string.format('%5d %s', line, x)
            send(x)
        end 
    end)
end
 
p = producer()
f = filter(p)
consumer(f)

Read the entire string auxiliary printed Sequence, see, Consumer executed, alarm call through the filter coroutine receive resume, while the filter comes in through the producer cried out again after resume receive the coroutine.

In talking about this, do you think the unix pipe? coroutine that is how multithreading kind. Use pipe, each task to be executed in the respective process, but with coroutine, each task is performed in the respective coroutine. pipe provides a buffer between the writer (producer) and the reader (consumer), thus opposing the operating speed is still quite possible. This is a very important characteristic of a pipe, because inter process communication, the cost is still a little big. Use coroutine, cost of switching between different task smaller, mostly a function call, therefore, writer and reader can almost be said to go hand in hand, ah.

Implement iterators with coroutine

We can put the iterator loop as is a special producer-consumer example: iterator produce, loop consume. Here we take a look at the powerful coroutine provides us with a coroutine to implement iterators.

We have to traverse the full array of arrays. Look at the common loop implementation code as follows:

function printResult(a)
    for i = 1, #a do
        io.write(a[i], ' ')
    end 
    io.write('\n')
end
 
function permgen(a, n)                                                                                                             
    n = n or #a
    if n <= 1 then
        printResult(a)
    else
        for i = 1, n do
            a[n], a[i] = a[i], a[n]
            permgen(a, n-1)
            a[n], a[i] = a[i], a[n]
        end 
    end 
end
 
permgen({1,2,3})
2 3 1 
3 2 1 
3 1 2 
1 3 2 
2 1 3 
1 2 3 

Coroutine employed to achieve the following:


function printResult(a)
    for i = 1, #a do
        io.write(a[i], ' ')
    end 
    io.write('\n')
end  
        
function permgen(a, n)
    n = n or #a
    if n <= 1 then
       coroutine.yield(a) 
    else
        for i = 1, n do
            a[n], a[i] = a[i], a[n]
            permgen(a, n-1)
            a[n], a[i] = a[i], a[n]
        end 
    end 
end  
        
function permutations(a)
    local co = coroutine.create(function () permgen(a) end)                                                                        
    return function ()
        local code, res = coroutine.resume(co)
        return res 
    end 
end  
        
for p in permutations({"a", "b", "c"}) do
    printResult(p)
end 
b c a 
c b a 
c a b 
a c b 
b a c 
a b c 

permutations in Lua function uses a conventional mode, to resume a coroutine encapsulated in the corresponding function. Lua provides a function coroutine.wap this pattern. Like with create, wrap creates a new coroutine, but does not return to the coroutine, but returns a function, this function is called, the corresponding coroutine to be awakened to run. With the original resume difference it is that the function does not return errcode as the first return value, if there is error occurred, quit (C-like language assert). Use wrap, permutations can be implemented as follows:

function permutations (a)
    return coroutine.wrap(function () permgen(a) end)
end

wrap with than create simple, it is really the return of the thing we need most: a coroutine can wake up the corresponding function. But not flexible enough. There is no way to check the status coroutine created the wrap, you can not check runtime-error (no return errcode, but directly assert)

Non-preemptive multithreading

We know, coroutine to run a series of collaborative multithreading. Each coroutine is equivalent to a thread. Of a yield-resume control may switch between different thread. However, with the conventional multithr different, coroutine is non-preemptive. A coroutine at run time, can not be other coroutine suspend it from the outside only by explicitly calling itself yield will hang, and relinquish control. For some programs, this is no problem, on the contrary, because the non-preemptive reason, the program easier. We do not need to worry about synchronization bug problem, because the synchronization between threads are the explicit. We just need to make sure to call the yield at the time on it.

However, the use of non-preemptive multithreading, regardless of which thread calls a blocking operation, the entire program will be blocked, this can not be tolerated. For this reason, many programmers do not think coroutine can replace the traditional multithread, but here we can see an interesting solution.

A typical multithreading scenarios: remote files by downloading more than http. Let's look at how to download a file, which requires the use LuaSocket library,

local socket = require("socket")

require("socket")

host = "www.w3.org"
file = "/standards/xml/schema"

c = assert(socket.connect(host, 80))
c:send("GET " .. file .. " HTTP/1.0\r\n\r\n") -- 注意GET后和HTTP前面的空格

while true do
    local s, status, partial = c:receive(2^10)
    io.write(s or partial)
    if status == "closed" then
        break
    end
end

c:close()

Now we know how to download a file of. Now back to the issue said earlier downloads of multiple remote files. When we receives a remote file, the program spent most of its time waiting for the arrival of data, that is, the call receive function is blocked. Therefore, if we can simultaneously download all the files, the program runs much faster speed. Here we look at how to use coroutine to simulate this implementation. We create a download task for each thread, there is no time data is available in a thread, it calls the program will yield control to a simple dispatcher, the dispatcher to wake up another thread. Let the code before the first written as a function, but there are a few changes, the output file will no longer content to stdout, while only between the output filesize.

function download(host, file)
    local c = assert(socket.connect(host, 80))
    local count = 0  --  counts number of bytes read
    c:send("GET " .. file .. " HTTP/1.0\r\n\r\n")
    while true do
        local s, status, partial = receive(c)
        count = count + #(s or partial)
        if status == "closed" then
            break
        end 
    end 
    c:close()
    print(file, count)
end
function receive (connection)
    return connection:receive(2^10)
end

However, if you want to download multiple files, then this function must be non-blocking receive data. In the absence of data received, call hang yield, relinquish control. Implementation should be as follows:

function receive(connection)   
    connection:settimeout(0)  -- do not block          
    local s, status, partial = connection:receive(2^10)
    if status == "timeout" then
        coroutine.yield(connection)
    end                        
    return s or partial, status
end

settimeout (0) is connected to this non-blocking mode. When the status changes to "timeout", meaning that the operation is not yet complete returns, and in this case, the thread will yield. Non-false parameter passed to the yield, telling the dispatcher thread is still running. Note that even if a timeout, the connection will still return what it has received, there is a partial variable.

The following code shows a simple dispatcher. Table threads hold a series of running thread. Function get sure that each task individually download a thread. dispatcher itself is a cycle, continuous through all thread, one by one to resume. If a download task has been completed, be sure to delete the thread from the thread table. When there is no thread running when the cycle stopped.

Finally, the program creates threads it needs, and calls the dispatcher. For example, four documents downloaded from w3c site, the program is as follows:

local socket = require("socket")

function receive(connection)
    connection:settimeout(0)  -- do not block
    local s, status, partial = connection:receive(2^10)
    if status == "timeout" then
        coroutine.yield(connection)
    end
    return s or partial, status
end

function download(host, file)
    local c = assert(socket.connect(host, 80))
    local count = 0  --  counts number of bytes read
    c:send("GET " .. file .. " HTTP/1.0\r\n\r\n")
    while true do
        local s, status, partial = receive(c)
        count = count + #(s or partial)
        if status == "closed" then
            break
        end
    end
    c:close()
    print(file, count)
end

threads = {}  -- list of all live threads

function get(host, file)
    -- create coroutine
    local co = coroutine.create(function ()
        download(host, file)
    end)
    -- intert it in the list
    table.insert(threads, co)
end

function dispatch()
    local i = 1
    while true do
        if threads[i] == nil then  -- no more threads?
            if threads[1] == nil then -- list is empty?
                break
            end
            i = 1  -- restart the loop
        end
        local status, res = coroutine.resume(threads[i])
        if not res then   -- thread finished its task?
            table.remove(threads, i)
        else
            i = i + 1
        end
    end
end

host = "www.w3.org"
get(host, "/TR/html401/html40.txt")
get(host, "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf")
get(host, "/TR/REC-html32.html")
get(host, "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt")
dispatch() -- main loop
/TR/html401/html40.txt  629
/TR/REC-html32.html 606
/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt    229699
/TR/2002/REC-xhtml1-20020801/xhtml1.pdf 115777

Running around 10s, 4 files have been downloaded completed

Again by blocking the download retry order a bit, take time becomes longer when a local test, do not know the network problem, blocking a multi-file download code is as follows, in fact, a code in the above paragraphs

local socket = require("socket")

function receive (connection)
    return connection:receive(2^10)
end

function download(host, file)
    local c = assert(socket.connect(host, 80))
    local count = 0  --  counts number of bytes read
    c:send("GET " .. file .. " HTTP/1.0\r\n\r\n")
    while true do
        local s, status, partial = receive(c)
        count = count + #(s or partial)
        if status == "closed" then
            break
        end 
    end 
    c:close()
    print(file, count)
end



host = "www.w3.org"

download(host, "/TR/html401/html40.txt")
download(host, "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf")
download(host, "/TR/REC-html32.html")
download(host, "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt")

In addition to speed, there is no other room for optimization, the answer is there. When there is no data reception thread, each thread Dispatcher traverses it has no data to look up, blocking this process could be the result of multiple versions of a 30-fold cost cpu.

To avoid this situation, we use the select function LuaSocket provided. It runs a program block while waiting for a set of sockets state changes. Code changes is relatively small, in the cycle, the table is connected to the collector connections timeout when all connections have a timeout, to wait for the call to select Dispatcher connection state changes. The version of the program, the bloggers development environment test, just less than 7s, download the complete four files, in addition to the consumption of a lot of cpu is small, only a little more than clog version only. The new dispatch code is as follows:

function dispatch()
    local i = 1 
    local connections = {}
    while true do
        if threads[i] == nil then  -- no more threads?
            if threads[1] == nil then -- list is empty?
                break
            end 
            i = 1  -- restart the loop
            connections = {}
        end       
        local status, res = coroutine.resume(threads[i])
        if not res then   -- thread finished its task?
            table.remove(threads, i)
        else   
            i = i + 1 
            connections[#connections + 1] = res 
            if #connections == #threads then   -- all threads blocked?
                socket.select(connections)
            end                                                                                                                    
        end       
    end           
end

Coroutine about completed, the above example of its implementation are well documented, for your reference.

Guess you like

Origin www.cnblogs.com/xiaoqiangink/p/12082996.html