• Lua协程


    Lua协程

    协同程序(coroutine)与多线程情况下的线程比较类似:有自己的堆栈、局部变量、指令指针,但与其它协程共享全局变量等很多信息。

    协程类似一种多线程,但与多线程还有很多区别:

    1. 协程并非os线程,所以创建、切换开销比线程相对要小。
    2. 协程与线程一样有自己的栈、局部变量等,但是协程的栈是在用户进程空间模拟的,所以创建、切换开销很小。
    3. 多线程程序是多个线程并发执行,也就是说在一瞬间有多个控制流在执行。而协程强调的是一种多个协程间协作的关系,只有当一个协程主动放弃执行权,另一个协程才能获得执行权,所以在某一瞬间,多个协程间只有一个在运行。
    4. 由于多个协程时只有一个在运行,所以对于临界区的访问不需要加锁,而多线程的情况则必须加锁。
    5. 多线程程序由于有多个控制流,所以程序的行为不可控,而多个协程的执行是由开发者定义的所以是可控的。

    Lua的协程是不对称的(asymmetric coroutines),是指“挂起一个正在执行的协同函数” 和 “使一个被挂起的协程再次执行的函数”是不同的。

    有些语言使用对称协同(symmetric coroutines),即使用同一个函数负责“执行与挂起间的状态切换”。

    1、状态切换

    协程有3个状态:

    1、suspended(挂起);

    2、running(运行);

    3、dead(停止);

    协程API: 

    co = coroutine.create(function)                         -->创建协程,返回thread类型
    
    coroutine.status(co)                                    -->检查协程状态
    
    coroutine.resume(co)                                    -->恢复运行
    
    coroutine.yield()                                       -->挂起

    协程创建成功后,处于suspended状态,此时并未运行。

    在协程中使用yield函数,可以让正在运行的代码挂起。

     一个例子:

    co = coroutine.create(function()
                for i=1,5 do
                    print("co", i)
                    coroutine.yield()
                end
            end)
        
    coroutine.resume(co)    -- 1
    coroutine.resume(co)    -- 2
    coroutine.resume(co)    -- 3
    coroutine.resume(co)    -- 4
    coroutine.resume(co)    -- 5
    print(coroutine.resume(co) )   -- true
    print(coroutine.resume(co) )   -- false cannot resume dead coroutine                                                                  

    注意:resume运行在保护模式下,如果协程内部存在错误,Lua并不会抛出错误,而是把错误返回给resume函数。

    协程的另一个作用是通过resume-yield来交换数据:

    co = coroutine.create(function(a, b)
            coroutine.yield(a+b, a-b)
            return 6,7
            end)
    
    print(coroutine.resume(co, 20, 10))     -- true 30 10
    print(coroutine.resume(co))          -- true 6 7

    可见,resume的参数会传递给协同函数,yield的参数会作为resume的返回值。并且,协程结束时的返回值也会传递给 resume。

    2、管道

    协同最具有代表性的例子是用来解决生产者-消费者问题,假定有一个函数不断地生产数据(比如从文件读取),另一个函数不断的处理这些数据(比如写到一个文件中),函数如下:

    function producer()
        while true do
            local x=io.read()
            send(x)
        end
    end 
    
    function consumer()
        while true do
            local x=receive()
            io.write(x, '
    ')
        end
    end

    上面的代码中,生产者和消费者都在不停的循环,而对对方的状态一无所知,我们需要改变一下结构,使得两者能够协调工作。

    以消费者驱动模型为例,一开始我们调用消费者,当消费者需要值时唤起生产者,生产者生产处数据后停止,直到消费者再次请求。

    完整的示例代码如下:

    function receive(prod)
        local status, value = coroutine.resume(prod)
        return value
    end 
    
    function send(x)
        local coroutine.yield(x)
    end 
    
    
    function producer()
        return coroutine.create(function ()
            while true do
                local x=io.read()
                send(x)
            end
        end)
    end
    
    function consumer(prod)
        while true do
            local x=receive(prod)
            io.write(x, '
    ')
        end
    end 
    
    function filter(prod)
        return coroutine.create(function()
                local line = 1
                while true do
                    local x=receive(prod)
                    x=string.format("%5d %s", line, x)
                    send(x)
                    line = line+1
                end
            end)
    end 
    
    consumer(filter(producer()))

    上面这个例子的工作方式非常类似UNIX的管道(pipe),协程是一种非抢占式的多线程。

    在pipe的方式下,每个任务在独立的进程中运行,进程间的切换代价比较高;

    而在协同中,每个任务运行在独立的协同代码中,任务间的切换代价较小,与函数调用相当。

    3、非抢占式多线程

    协程是一种非抢占式的多线程,这句话的含义是:

    当一个协程正在运行时,不能在外部终止它,只能通过显式调用yield挂起它的执行。

    显然,非抢占式的多线程比较容易写,因为不需要考虑线程同步带来的bug。

    非抢占式的多线程的弊端在于,不管什么时候,只要有一个线程调用一个阻塞操作(blocking operation),整个程序在阻塞操作完成之前都将停止。

    协同的这种弊端有点让人难以忍受!

    看一个多线程的例子,通过HTTP协议从远程服务器下载一些文件。

    在下载过程中,如果遇到阻塞,挂起线程,并使用一个调度器去resume另一个线程。

    require "luasocket"
    
    function receive(connection)
        connection:timeout(0)   -- do not block
        local s,status=connection:receive(2^10)
        if status=="timeout" then
            coroutine.yield(connection)
        end 
        return s, status
    end
    
    function download(host, file)
        local c=assert(socket.connection(host, 80))
        local count = 0 
        c:send("GET" .. file .. " HTTP/1.0
    
    ")
        while ture do
            local s, status=receive(c)
            count=count+string.len(s)
            if status=="closed" then break end    
        end 
        c:close()
        print(file, count)
    end
    
    threads = {}
    function get(host, file)
        local co=coroutine.create(function()
                    download(host, file)
                end)
    
        table.insert(threads, co) 
    end
    
    function dispatcher() while ture do local n=#threads if n==0 then break end local connections={} for i=1,n do local status,res=coroutine.resume(threads[i]) if not res then      -- finish table.remove(threads, i) break else  -- timeout table.insert(connections, res) end end if #connections == n then socket.select(connections) end end end host="http://news.163.com/" get(host, "/14/0330/17/9OJO5ML800014JB6.html") get(host, "/14/0330/08/9OIQKNS90001124J.html") get(host, "/14/0330/10/9OJ2M5PN000915BF.html") dispatch()
  • 相关阅读:
    Typescript---01 数据类型
    微信小程序开发01 --- 微信小程序项目结构介绍
    C# 高级编程05----常用修饰符
    C# 高级编程04----类
    C# 高级编程02----手动创建C#程序
    C# 高级编程03----细节内容
    C# 高级编程01----.Net基础介绍
    Docker----起步(2)----在Ubuntu上安装最新版的Docker CE
    Microsoft Graph API -----起题 Graph API
    Docker----与Asp.net core 的完美结合,在docker容器中创建Asp.Net Core 项目
  • 原文地址:https://www.cnblogs.com/chenny7/p/3634529.html
Copyright © 2020-2023  润新知