Take – Coroutines

Q: What is “Coroutine”?

A: Some are similar to multi-threading, but there are differences between them.
1. Conceptually, multi-threading is all at the same time Threads are all running at the same time. A group of “Coroutines” has only one “Coroutine” running at the same time.
2. From the perspective of application scenarios, multi-threading generally plays a role of shunting. Each thread concentrates on doing its own thing, and the cooperation relationship between threads is weak. The cooperative relationship between a group of “Coroutines” is stronger. They are all doing the same thing, and they share the work in this thing.

Q: How to use “Coroutines”?

A:

< span class="hljs-comment">-- Create "Coroutines". co = coroutine.create(function< span class="hljs-params">() print("hi" span>) end)print(type (co)) --> thread - "Coroutine" is a "thread". -- "Coroutine" has 3 states: suspended, running, and dead. -- When "Coroutine" is created, it is suspended by default. print(coroutine.status(co)) --> suspended-- Run "Coroutine". coroutine.resume(co) --> hi-- "Coroutine" becomes dead after running. print(coroutine.status(co)) --> dead

At present, it seems that “Coroutine” is not much different from ordinary function calls, but it is really powerful It lies in coroutine.yield(), it can make the running “Coroutine” hang,

co = coroutine.create( function () for i=1, 10 do print("co", i) coroutine.yield() -- Hang "Coroutine".  end end)coroutine .resume(co) --> co 1coroutine.resume(co) --> co 2coroutine.resume(co) --> co 3...co) --> co 10coroutine.resume(co) -- prints nothing - "for" loop ends. 

and coroutine.resume()and coroutine.yield() can easily exchange data,

-- You can pass parameters to the function called by "coroutine" through "coroutine.resume()". co = coroutine.create(function< span class="hljs-params">(a, b) print("co ", a, b) end)coroutine.resume(co, 1, 2) --> co 1 2--[[ You can pass parameters to "coroutine.resume()" through "coroutine.yield()". The parameters passed by "coroutine.yield()" are presented by the return value of "coroutine.resume()". ]]co = coroutine.create(function(a, b) for i = 1, 3 do print span>(i, a, b) coroutine.yield(i, a + b, a-b) end end)print(coroutine. resume(co, 20, 10))print span>(coroutine.resume(co, 50, 10 ))print(coroutine.r esume(co, 70, 90))--[ [ result. It can be seen here that the first parameter passed by "coroutine.resume()" will be used as the parameter passed every time after "coroutine.resume()", regardless of whether the subsequent "coroutine.resume()" is specified or not New parameters. ]]1 20 10true 1 30 102 20 10true 2 30 103 20 10true 3 30 10--[[ Symmetrical, you can pass "coroutine.resume()" to "coroutine .yield()" passes parameters. The parameters passed by "coroutine.resume()" are presented by the return value of "coroutine.yield()". ]]co = coroutine.create (function(a, b) for i = 1, 3 do print span>(i, a, b) print("co", coroutine.yield()) end end)coroutine.resume(co, "first", "call", "discard")coroutine.resume(co, "second ", "call")coroutine.resume(co, "third", "call")coroutine.resume(co, "fourth", "call")< span class="hljs-comment">--[[ Results. It can be seen here that 1. The extra parameters passed in the first "coroutine.resume()" after the creation of "coroutine" are discarded. 2. Same as the above example, the parameters passed by the first "coroutine.resume()" will be used as the parameters passed by each subsequent "coroutine.resume()", regardless of the subsequent "coroutine.resume()" Whether a new parameter is specified. 3. The parameters passed by "coroutine.resume()" after the first time will be regarded as parameters passed to "coroutine.yield()". 4. When "coroutine" is suspended, "coroutine.yield()" does not return. It is guessed that it may have been suspended inside "coroutine.yield()". Later, when "coroutine" is restored, "coroutine.yield()" returns. It is guessed that it may continue to run from "coroutine.yield()" to the end of "coroutine.yield()" and return. The above phenomenon corresponds to the running process of "coroutine". The first time "coroutine.resume()" after the creation of "coroutine" starts from the function specified when "coroutine" is created, and every time after "coroutine." resume()" is to start running from the place where "coroutine.yield()" internally paused. ]]1 first callco second call2 first callco third call3 first callco fourth call--[[ When "coroutine" transitions to a dead state, the value returned by the function specified when "coroutine" was created It will also be passed to "coroutine.resume()", which is represented by the return value of "coroutine.resume()". ]]co = coroutine.create(function () for i = 1< /span>, 3 do coroutine. yield() end return 6, < span class="hljs-number">7 end)print(coroutine.resume(co)) --> trueprint (coroutine.resume(co)) --> true print(coroutine.resume(co)) --> trueprint(coroutine.resume( co)) --> true 6 7

Q: Examples of using "Coroutines" ?

A: A typical example of a cooperative program is the producer and consumer problem. For example, one function continuously produces values ​​(read data from a file, in this case from standard input), another function continuously consumes values ​​(write data to another file, in this case, to standard output Write).

function< /span> receive () local status, value = coroutine.resume(producer) return valueendfunction send (x) coroutine.yield(x)< span class="hljs-keyword">endfunction consumer () while true do local< /span> x = receive() -- receive from producer io.write(x, "
") -- consume new value endendproducer = coroutine.create(function () while true do local  x = io.read() -- produce new value send(x) -- send to consumer end end)consumer() -- From Consumers start. --[[ Consumers need to receive data to write data, call "receive()", "receive ()" will resume the operation of "coroutines" internally, and wait for the data, so that the lead is in the hands of the producer, who reads the data and sends the data , Call "send()", "send()" internal suspension"coroutines", at this time the data is passed to  through "coroutine.yield()" "coroutine.resume()", "coroutine.resume()" returns data to "receive() ", write data. Back and forth. ]]

We can extend this example with filters. Filters are between producers and consumers, and data can be converted
handle. The filter is both a producer and a consumer at the same time. To be more specific, the filter is at the same time, for the real producer, it is the consumer, and for the real consumer, it is the producer.
We modify the above example to add the function of printing line numbers,

function receive (prod ) local status, value = coroutine.resume(prod) return valueend< /span>function send (x) coroutine.yield(x)end< span class="hljs-function">function producer () return coroutine.create(function () while true do local x = io.read() -- Suspend the "coroutines" where the producer (ie yourself) is located, and return the data to the filter.  send(x) end end)endfunction filter (prod) return coroutine.create< /span>(function () local line = 1 while true do -- restore the "coroutines" where the producer is located, so that the producer can provide Data. local x = receive(prod) x = string.format("%5d %s", line, x) -- line No.  -- Suspend the "coroutines" where the filter (ie yourself) is located, and return the data to the consumer.  send(x) line = line< /span> + 1 end end)end< /span>function consumer (prod) while true do -- Restore the "coroutines" where the filter is located, so that the filter can provide data.  local x = receive(prod) io.write(x, "
") endend--[[ "producer()" creates a group of "coroutines", Filter control;  "filter()" created a set of "coroutines", which was created by the consumer Take control. ]]consumer(filter(producer()))

The above example may be natural It makes you think of "UNIX" pipes. In the pipeline mode, each task runs in an independent process, while in the "coroutines" mode, each task runs in an independent "coroutine". The cost of switching between processes is very high, and the cost of switching "coroutine" is roughly equivalent to switching between functions.

Q: How to use "Coroutines" to realize "Iterator"?

A: "Iterator" is a typical "producer-consumer" model, where the value is generated by the "Iterator" and the value is consumed by the loop body . The following is an example of printing the full arrangement of elements in an array. Let’s use recursion to implement it first.

-- The algorithm is very simple, that is, put each element in the array to the end of the array in turn, and then calculate the total permutation of the array composed of the remaining elements. function permgen (a, n) if n == < span class="hljs-number">0 then printResult(a) else for i=1,n do -- put i-th element as the last one a[n], a[i] = a[i], a[n] -- generate all permutations of the other elements permgen(a, n-1) -- restore i-th element a[n], a [i] = a[i], a[n] end end< /span>endfunction printResult (a) for i,v in ipairs(a) do io.write(v, " ") end io.write("
")endpermgen ({< span class="hljs-number">1,2,3,4}, 4)

Connect Next, the implementation of converting it to "Coroutines" is very simple,

function permgen (a, n) if n ==  0 then -- Once the arrangement result is obtained, "coroutine" hangs, and the result is returned to "coroutine.resume" ()".  coroutine.yield(a) else for  i=1,n do -- put i-th element as the last one a[n], a[i] = a[i], a[n] -- generate all permutations of the other elements permgen(a, n-1) -- restore i-th element a[n], a span>[i] = a[i], a[n] end endend-- Create "coroutine" internally and return "iterator function". function perm (a) local n = #a - Get the size of the array .  -- "coroutine" is responsible for generating permutation results in a recursive manner.  local co = coroutine.create(< span class="hljs-keyword">function () permgen(a, n) end) -- "iterator function" Responsible for restoring "coroutine" to get the ranking result from "coroutine.yield".  return function () local code, res = coroutine.resume(co) return res endend< span class="hljs-function">function printResult ( a) for i,v in ipairs(a) do io.write(v, " ") end io.write("
")endfor p in perm{"a", "b", "c"} do printResult(p)end

perm() uses a common pattern in Lua: the operation of restoring "coroutine" is encapsulated in a function. This mode is often used in Lua, so Lua provides coroutine.wrap() to achieve this operation. Compared with coroutine.create(), coroutine.create() returns "coroutine" itself, and coroutine.wrap() calls coroutine.resume(), wait for coroutine. resume() returns and returns its result. When coroutine.resume() successfully restores "coroutine", coroutine.wrap() returns coroutine.resume() except for the first return value (Ie "errcode", true or false). And when coroutine.resume() fails to restore "coroutine", coroutine.wrap() directly report an error.
So, we can change the original implementation of perm() to use coroutine.wrap(),

function perm (a)  local n = #a return coroutine.wrap(function () permgen(a, n) end)end

Q:如何使用”coroutines”实现抢占式线程?

A:”coroutines”是非抢占式的协作线程,这意味着在明确的指明要他停止(通过调用coroutine.yield())之前,他不能被其他线程打断,这种机制在某些需要高实时性的场合是不能被接收的。 
抢占式线程的“抢占”其原理就在于CPU为每个线程分配一个时间片,当时间片耗尽而线程的工作还没有完成时,CPU就强制将该线程暂停同时让另一个线程开始工作。 
使用”coroutines”也可以实现抢占式线程,其原理就在于由我们自己为每一对”coroutine”分配时间片。以下的例子简单的实现了抢占式线程,每个线程的工作都是计算某个整数区间内所有整数之和,

threads = {}    -- 调度表。所有工作中的线程都会存入此表。 time = os.time()    -- 每个线程开始工作时的时间。当线程被挂起时会更新这个时间。 limit_time = 1    -- 时间片。每个线程给1s的工作时间。 -- 计算某个整数区间内所有整数之和。 function cal(from, to)    local sum = 0;    for i = from, to do        sum = sum + i        -- 时间片耗尽,而工作还没有完成。         if (os.time() - time) >= limit_time then            -- 打印计算进度。             print(string.format("Worker %d calculating, %f%%.", worker, (i / to * 100)))            time = os.time()    -- 当进程被挂起时更新时间,下一个进程将以此作为开始工作时的时间。             coroutine.yield()    -- 休息。         end    end    -- 工作完成,打印计算结果。     print(string.format("Worker %d finished, %d.", worker, sum))end-- 分配任务。 function job (from, to)    -- 创建"coroutine"。     local co = coroutine.create(function ()        cal(from, to)    end)    table.insert(threads, co)    -- 将线程加入调度表。 end-- 4个线程,分别计算不同整数区间内所有整数之和。 job(1, 100000000)job(10, 50000000)job(5000, 6000000)job(10000, 70000000)-- 分发器。调度所有线程的运行。 while true do    local n = #threads    if n == 0 then break end   -- 没有线程需要工作了。     for i = 1, n do        worker = i    -- 表示哪个线程在工作。         local status = coroutine.resume(threads[i])    -- 恢复"coroutine"工作。         if not status then    -- 线程是否完成了他的工作? "coroutine"完成任务时,status是"false"。             table.remove(threads, i)    -- 将线程从调度表中删除。             break        end    endend

Worker 1 calculating, 0.482016%. 
Worker 2 calculating, 10.191070%. 
Worker 3 calculating, 85.029767%. 
Worker 4 calculating, 7.273926%. 
Worker 1 calculating, 5.534152%. 
Worker 2 calculating, 20.370044%. 
Worker 3 finished, 17999990502500. <– 3号线程完成工作。 
Worker 4 calculating, 13.201869%. 
Worker 1 calculating, 10.590240%. 
Worker 2 calculating, 30.563072%. 
Worker 1 calculating, 15.656026%. 
Worker 2 calculating, 40.731870%. 
Worker 3 calculating, 20.430621%. <– 原先的4号线程变为3号线程。 
Worker 1 calculating, 20.689852%. 
Worker 2 calculating, 50.912444%. 
Worker 3 calculating, 27.682421%. 
Worker 1 calculating, 25.750115%. 
Worker 2 calculating, 61.074508%. 

实现的思路也很清晰。一个调度器拥有一张调度表,其中存储了需要工作的线程。调度器为每个线程分配一个时间片,当发现线程的时间片耗尽,则挂起线程同时让调度表中的下一个开始工作。当发现线程的工作完成时,则从调度表中移除该线程。总的来说,通过调度器与时间片,使用”coroutines”实现了简单的抢占式线程。

附加:

1、coroutine.resume()会返回一个”bool”值,表示是否成功的恢复了挂起的”Coroutine”,

-- 处于挂起状态的"Coroutine"可以恢复。 print(coroutine.status(xxx))    --> suspendedprint(coroutine.resume(xxx))    --> true-- 已处于死亡状态的"Coroutine"无法恢复。 print(coroutine.status(xxx))    --> deadprint(coroutine.resume(xxx))    --> false cannot resume dead coroutine

2、在生产者和消费者问题的第一个例子中, 
开始时调用消费者,当消费者需要值时他唤起生产者生产值,生产者生产值后停止直到消费者再次请求。我们称这种设计为消费者驱动的设计。 
3、”coroutines”是一种协作的多线程。每一个”coroutine”相当于一个线程。 ”yield-resume”的组合可以在线程之间互相转换。然而,区别于真正的多线程,”coroutines”是非抢占的。当一个”coroutine”在运行的时候,他不能被其他的线程所打断,除非明确的指明要他停止(通过调用coroutine.yield())。编写非抢占式的多线程也比编写抢占式的多线程容易的多,因为你不用担心线程之间的同步问题所造成的bugs,你只需要确定当”coroutine”被挂起时他不是处在临界区。

Q:什么是”Coroutine”?

A:有些类似于多线程,但他们之间也有区别, 
1、从概念上来看,多线程是同一时间所有的线程同时都在运行。而一组”Coroutines”在同一时间只有一个”Coroutine”在运行。 
2、从应用场景来看,多线程一般起到分流的作用,每个线程专注做自己的事情,线程之间合作的关系较弱。而一组”Coroutines”之间合作的关系就比较强,他们都是在做同一件事情,他们分摊了这件事情中的工作。

Q:如何使用”Coroutines”?

A:

-- 创建"Coroutines"。 co = coroutine.create(function()         print("hi")     end)print(type(co))    --> thread -- "Coroutine"是个"thread"。 -- "Coroutine"有3种状态:挂起,运行,死亡。 -- 当"Coroutine"被创建之后默认是挂起状态。 print(coroutine.status(co))    --> suspended-- 运行"Coroutine"。 coroutine.resume(co)    --> hi-- "Coroutine"在运行完成之后变为死亡状态。 print(coroutine.status(co))    --> dead

目前看起来”Coroutine”与普通的函数调用没有太大区别,但他真正强大的地方在于coroutine.yield(),它可以让运行中的”Coroutine”挂起,

co = coroutine.create(function ()         for i=1, 10 do             print("co", i)             < span class="hljs-built_in">coroutine.yield()    -- 挂起"Coroutine"。          end     end)coroutine.resume(co)    --> co 1coroutine.resume(co)    --> co 2coroutine.resume(co)    --> co 3...co)    --> co 10coroutine.resume(co)    -- prints nothing -- "for"循环结束。 

以及coroutine.resume()coroutine.yield()之间可以方便的交换数据,

-- 可以通过"coroutine.resume()"向"coroutine"调用的函数传递参数。 co = coroutine.create(function(a, b)         print("co", a, b)     end)coroutine.resume(co, 1, 2)    --> co 1 2--[[ 可以通过"coroutine.yield()"向"coroutine.resume()"传递参数。 "coroutine.yield()"传递的参数由"coroutine.resume()"的返回值呈现。 ]]co = coroutine.create(function(a, b)         for i = 1, 3 do             print(i, a, b)             coroutine.yield(i, a + b, a - b)         end     end)print(coroutine.resume(co, 20, 10))print(coroutine.resume(co, 50, 10))print(coroutine.r esume(co, 70, 90))--[[ 结果。这里可以看到,第一次"coroutine.resume()"所传递的参数, 将作为之后每次"coroutine.resume()"所传递的参数, 无论之后的"coroutine.resume()"是否指定了新的参数。 ]]1   20  10true    1   30  102   20  10true    2   30  103   20  10true    3   30  10--[[ 对称的,可以通过"coroutine.resume()"向"coroutine.yield()"传递参数。 "coroutine.resume()"传递的参数由"coroutine.yield()"的返回值呈现。 ]]co = coroutine.create (function(a, b)         for i = 1, 3 do             print(i, a, b)             print("co", coroutine.yield())         end     end)coroutine.resume(co, "first", "call", "discard")coroutine.resume(co, "second", "call")coroutine.resume(co, "third", "call")coroutine.resume(co, "fourth", "call")--[[ 结果。这里可以看到, 1、从创建"coroutine"之后的第一次"coroutine.resume()" 传递的多余参数被丢弃了。 2、与上面的例子相同,第一次"coroutine.resume()"所传递的参数, 将作为之后每次"coroutine.resume()"所传递的参数, 无论之后的"coroutine.resume()"是否指定了新的参数。 3、第一次之后的"coroutine.resume()"所传递的参数 都会被视作传递给"coroutine.yield()"的参数。 4、当"coroutine"被挂起时,"coroutine.yield()"并不返回, 猜测可能是在"coroutine.yield()"内部暂停了。之后当"coroutine"被恢复时,"coroutine.yield()"才返回, 猜测可能是从"coroutine.yield()"内部继续运行到"coroutine.yield()"结束返回。以上的现象与"coroutine"的运行流程相对应, 在创建"coroutine"之后的第一次"coroutine.resume()" 是从创建"coroutine"时指定的函数开始运行, 之后的每次"coroutine.resume()" 是从"coroutine.yield()"内部暂停的地方开始运行。 ]]1   first   callco  second  call2   first   callco  third   call3   first   callco  fourth  call--[[ 当"coroutine"转换为死亡状态时, 创建"coroutine"时所指定的函数所返回的值也会传递给"coroutine.resume()", 由"coroutine.resume()"的返回值呈现。 ]]co = coroutine.create(function ()         for i = 1, 3 do             coroutine.yield()         end         return 6, 7     end)print(coroutine.resume(co))   --> trueprint(coroutine.resume(co))   --> trueprint(coroutine.resume(co))   --> trueprint(coroutine.resume(co))   --> true 6 7

Q:使用”Coroutines”的例子?

A:协同程序的一个典型例子是生产者和消费者问题。比如一个函数不断产生值(从一个文件中读取数据,此例中是从标准输入中读取),另一个函数不断消耗值(将数据写到另一个文件中,此例中是向标准输出写)。

function receive ()    local status, value = coroutine.resume(producer)    return valueendfunction send (x)    coroutine.yield(x)endfunction consumer ()    while true do        local x = receive()        -- receive from producer        io.write(x, "
")          -- consume new value    endendproducer = coroutine.create(function ()    while true do        local x = io.read()     -- produce new value        send(x)                 -- send to consumer    end< /span>end)consumer()     -- 从消费者开始。 --[[ 消费者写数据需要先接收数据,调用"receive()",     "receive()"内部会恢复"coroutines"的运行,并等待数据,     这样主导权到了生产者手中,     生产者读取数据并发送数据,调用"send()""send()"内部挂起"coroutines",     此时数据通过"coroutine.yield()"传递给了"coroutine.resume()""coroutine.resume()"返回数据给"receive()",写数据。循环往复。 ]]

我们可以过滤器扩展这个例子,过滤器在生产者与消费者之间,可以对数据 
进行某些转换处理。过滤器在同一时间既是生产者,也是消费者。说的更具体一些,过滤器在同一时间,对于真正的生产者,它是消费者,对于真正的消费者,它是生产者。 
我们修改上面的例子,加入打印行号的功能,

function receive (prod)    local status, value = coroutine.resume(prod)    return valueendfunction send (x)    coroutine.yield(x)endfunction producer ()    return coroutine.create(function ()        while true do            local x = io.read()            -- 挂起producer(即自己)所在的"coroutines",返回给filter数据。             send(x)        end    end)endfunction filter (prod)    return coroutine.create(function ()        local line = 1        while true do            -- 恢复producer所在的"coroutines",以让producer提供数据。            local x = receive(prod)            x = string.format("%5d %s", line, x)    -- 行号。             -- 挂起filter(即自己)所在的"coroutines",返回给consumer数据。             send(x)            line = line + 1        end    end)endfunction consumer (prod)    while true do        -- 恢复filter所在的"coroutines",以让filter提供数据。         local x = receive(prod)        io.write(x, "
")    endend--[[ "producer()"创建了一组"coroutines",由filter掌控;     "filter()"创建了一组"coroutines",由consumer掌控。 ]]consumer(filter(producer()))

上面这个例子可能很自然的让你想到”UNIX”的管道。管道的方式下每一个任务在独立的进程中运行,而”coroutines”方式下每个任务运行在独立的”coroutine”中。进程之间的切换代价很高,而”coroutine”的切换的代价大致相当于函数之间的切换。

Q:如何使用”Coroutines”实现”Iterator”?

A:”Iterator”是一种典型的“生产者 - 消费者”模式,由”Iterator”产生值,由循环体消耗值。下面是一个打印数组内元素全排列的例子,我们先用递归来实现它,

-- 算法很简单,就是依次将数组中每一个元素放到数组的最后,然后计算余下元素组成的数组的全排列。 function permgen (a, n)    if n == 0 then        printResult(a)    else        for i=1,n do            -- put i-th element as the last one            a[n], a[i] = a[i], a[n]            -- generate all permutations of the other elements            permgen(a, n - 1)            -- restore i-th element            a[n], a[i] = a[i], a[n]        end    endendfunction printResult (a)    for i,v in ipairs(a) do        io.write(v, " ")    end    io.write("
")endpermgen ({1,2,3,4}, 4)

接下来,将其转换为”Coroutines”的实现方式就很简单了,

function permgen (a, n)    if n == 0 then        -- 一旦得到排列结果,"coroutine"挂起,返回结果给"coroutine.resume()"。         coroutine.yield(a)    else        for i=1,n do            -- put i-th element as the last one            a[n], a[i] = a[i], a[n]            -- generate all permutations of the other elements            permgen(a, n - 1)            -- restore i-th element            a[n], a[i] = a[i], a[n]        end    endend-- 内部创建"coroutine"以及返回"iterator function"。 function perm (a)    local n = #a -- 获得数组的大小。     -- "coroutine"负责以递归的方式产生排列结果。     local co = coroutine.create(function () permgen(a, n) end)    -- "iterator function"负责恢复"coroutine"以从"coroutine.yield"得到排列结果。     return function ()        local code, res = coroutine.resume(co)        return res    endendfunction printResult (a)    for i,v in ipairs(a) do        io.write(v, " ")    end    io.write("
")endfor p in perm{"a", "b", "c"} do    printResult(p)end

perm()使用了Lua中一种常用的模式:将恢复”coroutine”的操作封装在一个函数里。这种模式在Lua中经常被使用,所以Lua提供coroutine.wrap()来实现这种操作。与coroutine.create()相比,coroutine.create()返回”coroutine”本身,而coroutine.wrap()在内部调用coroutine.resume()后,等待coroutine.resume()返回并返回其结果。当coroutine.resume()成功恢复了”coroutine”时,coroutine.wrap()返回coroutine.resume()除了第一个返回值(即”errcode”,true or false)外的其他返回值。而当coroutine.resume()恢复”coroutine”失败时,coroutine.wrap()直接报错。 
所以,我们可以将原先perm()的实现更改为使用coroutine.wrap()

function perm (a)  local n = #a  return coroutine.wrap(function () permgen(a, n) end)end

Q:如何使用”coroutines”实现抢占式线程?

A:”coroutines”是非抢占式的协作线程,这意味着在明确的指明要他停止(通过调用coroutine.yield())之前,他不能被其他线程打断,这种机制在某些需要高实时性的场合是不能被接收的。 
抢占式线程的“抢占”其原理就在于CPU为每个线程分配一个时间片,当时间片耗尽而线程的工作还没有完成时,CPU就强制将该线程暂停同时让另一个线程开始工作。 
使用”coroutines”也可以实现抢占式线程,其原理就在于由我们自己为每一对”coroutine”分配时间片。以下的例子简单的实现了抢占式线程,每个线程的工作都是计算某个整数区间内所有整数之和,

threads = {}    -- 调度表。所有工作中的线程都会存入此表。 time = os.time()    -- 每个线程开始工作时的时间。当线程被挂起时会更新这个时间。 limit_time = 1    -- 时间片。每个线程给1s的工作时间。 -- 计算某个整数区间内所有整数之和。 function cal(from, to)    local sum = 0;    for i = from, to do        sum = sum + i        -- 时间片耗尽,而工作还没有完成。         if (os.time() - time) >= limit_time then            -- 打印计算进度。             print(string.format("Worker %d calculating, %f%%.", worker, (i / to * 100)))            time = os.time()    -- 当进程被挂起时更新时间,下一个进程将以此作为开始工作时的时间。             coroutine.yield()    -- 休息。         end    end    -- 工作完成,打印计算结果。     print(string.format("Worker %d finished, %d.", worker, sum))end-- 分配任务。 function job (from, to)    -- 创建"coroutine"。     local co = coroutine.create(function ()        cal(from, to)    end)    table.insert(threads, co)    -- 将线程加入调度表。 end-- 4个线程,分别计算不同整数区间内所有整数之和。 job(1, 100000000)job(10, 50000000)job(5000, 6000000)job(10000, 70000000)-- 分发器。调度所有线程的运行。 while true do    local n = #threads    if n == 0 then break end   -- 没有线程需要工作了。     for i = 1, n do        worker = i    -- 表示哪个线程在工作。         local status = coroutine.resume(threads[i])    -- 恢复"coroutine"工作。         if not status then    -- 线程是否完成了他的工作? "coroutine"完成任务时,status是"false"。             table.remove(threads, i)    -- 将线程从调度表中删除。             break        end    endend

Worker 1 calculating, 0.482016%. 
Worker 2 calculating, 10.191070%. 
Worker 3 calculating, 85.029767%. 
Worker 4 calculating, 7.273926%. 
Worker 1 calculating, 5.534152%. 
Worker 2 calculating, 20.370044%. 
Worker 3 finished, 17999990502500. <– 3号线程完成工作。 
Worker 4 calculating, 13.201869%. 
Worker 1 calculating, 10.590240%. 
Worker 2 calculating, 30.563072%. 
Worker 1 calculating, 15.656026%. 
Worker 2 calculating, 40.731870%. 
Worker 3 calculating, 20.430621%. <– 原先的4号线程变为3号线程。 
Worker 1 calculating, 20.689852%. 
Worker 2 calculating, 50.912444%. 
Worker 3 calculating, 27.682421%. 
Worker 1 calculating, 25.750115%. 
Worker 2 calculating, 61.074508%. 

实现的思路也很清晰。一个调度器拥有一张调度表,其中存储了需要工作的线程。调度器为每个线程分配一个时间片,当发现线程的时间片耗尽,则挂起线程同时让调度表中的下一个开始工作。当发现线程的工作完成时,则从调度表中移除该线程。总的来说,通过调度器与时间片,使用”coroutines”实现了简单的抢占式线程。

附加:

1、coroutine.resume()会返回一个”bool”值,表示是否成功的恢复了挂起的”Coroutine”,

-- 处于挂起状态的"Coroutine"可以恢复。 print(coroutine.status(xxx))    --> suspendedprint(coroutine.resume(xxx))    --> true-- 已处于死亡状态的"Coroutine"无法恢复。 print(coroutine.status(xxx))    --> deadprint(coroutine.resume(xxx))    --> false cannot resume dead coroutine

2、在生产者和消费者问题的第一个例子中, 
开始时调用消费者,当消费者需要值时他唤起生产者生产值,生产者生产值后停止直到消费者再次请求。我们称这种设计为消费者驱动的设计。 
3、”coroutines”是一种协作的多线程。每一个”coroutine”相当于一个线程。 ”yield-resume”的组合可以在线程之间互相转换。然而,区别于真正的多线程,”coroutines”是非抢占的。当一个”coroutine”在运行的时候,他不能被其他的线程所打断,除非明确的指明要他停止(通过调用coroutine.yield())。编写非抢占式的多线程也比编写抢占式的多线程容易的多,因为你不用担心线程之间的同步问题所造成的bugs,你只需要确定当”coroutine”被挂起时他不是处在临界区。

Leave a Comment

Your email address will not be published.