使用 Lua 协程模拟 Golang 的 go defer 编程模式

封装 go 函数

使用 Lua 协程处理异步回调函数 中已经介绍

这里简要列下:

  1. 封装 go 函数

    lua 复制代码
    ---go 函数创建并启动一个协程
    ---@param _co_task function @函数原型 fun(_co:thread)
    function go(_co_task)
        local co = coroutine.create(_co_task) -- 创建的协程是暂停的
        coroutine.resume(co, co)              -- 调用 coroutine.resume 激活协程执行
    end
  2. 封装项目中异步函数

    lua 复制代码
    ---封装 c_model.c_foo 异步函数,成为协程函数
    ---@param _co thread @协程对象
    ---@return boolean,string
    function co_foo(_co)
        c_model.c_foo(function(_ok, _result)
            coroutine.resume(_co, _ok, _result) -- 2. 回调函数被调用后,激活本协程继续执行。并把_ok, _result传递给 yield
        end)
        return coroutine.yield()                -- 1. 主动放弃运行,本协程被切换出去
    end
  3. 使用例子

    lua 复制代码
    ---test顺序编写代码,解决回调函数造成同块逻辑被撕裂的例子
    ---@param _co thread @协程对象
    function test(_co)
        for i = 1, 10, 1 do
            local ok, result = co_foo(_co) -- co_foo 会先 yield 切出;内部回调被执行时, resume 重新切回来继续执行
            print(ok, result)
        end
    end
    
    -- 启动 test 协程
    go(test)

封装 defer

defer 的特点有以下:

  • 协程正常退出能被执行
  • 协程异常退出能被执行
  • 同个协程内可以多次调用 defer
  • defer 被执行时,按出栈顺序被执行
defer 多次执行

首先定义 defer 函数,让它具备能多次被调用:

lua 复制代码
function defer(_co_wrap, h)
    table.insert(_co_wrap.defer_handlers, h)
end

因为要对 defer 的函数句柄做保持,以便退出时执行。包裹了下 co 对象:

lua 复制代码
---@class co_wrap
---@field co thread
---@field defer_handlers fun(_co_error:co_error)[]

同时定义下让 defer 的函数知道是否有错误的对象:

lua 复制代码
---@class co_error
---@field ok boolean
defer 被执行时,按出栈顺序被执行
lua 复制代码
function invoke_defer_handlers(_co_wrap, _co_error)
    for i=#_co_wrap.defer_handlers, 1, -1 do
        local h = _co_wrap.defer_handlers[i]
        xpcall(h, function(err) print(err) end, _co_error)
    end
end
协程异常时,能被执行

Lua 协程异常,通过 coroutine.resume 捕获,并返回错误信息

因此主要封装下 coroutine.resume :

lua 复制代码
function coroutine_resume(_co_wrap, ...)
    local ok, errmsg = coroutine.resume(_co_wrap.co, ...)
    if not ok then
        invoke_defer_handlers(_co_wrap, {ok=false}) -- 异常退出
    end
end
协程正常退出时,能被执行
lua 复制代码
function go(_co_task)
	local co = coroutine.create(function(_co_wrap)
        _co_task(_co_wrap)
        invoke_defer_handlers(_co_wrap, {ok=true}) -- 正常退出
    end)
    local cowrap = { co = co, defer_handlers = {} } ---@type co_wrap
    coroutine_resume(cowrap, cowrap) -- 初创建的协程是暂停的,手动触发执行
end

以上就可以在 Lua 中完全 Golang 的方式编写协程代码了

协程间通信

由于项目中暂时是一根线程管理一个 lua_state 对象,因此暂时无需求多线程中的协程间的通信需求

待续

相关推荐
努力--坚持14 分钟前
电商项目-网站首页高可用(一)
nginx·lua·openresty
黄名富5 小时前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
染指11109 小时前
50.第二阶段x86游戏实战2-lua获取本地寻路,跨地图寻路和获取当前地图id
c++·windows·lua·游戏安全·反游戏外挂·游戏逆向·luastudio
hkNaruto11 小时前
【P2P】【Go】采用go语言实现udp hole punching 打洞 传输速度测试 ping测试
golang·udp·p2p
入 梦皆星河11 小时前
go中常用的处理json的库
golang
海绵波波10713 小时前
Gin-vue-admin(2):项目初始化
vue.js·golang·gin
每天写点bug13 小时前
【go每日一题】:并发任务调度器
开发语言·后端·golang
一个不秃头的 程序员13 小时前
代码加入SFTP Go ---(小白篇5)
开发语言·后端·golang
基哥的奋斗历程14 小时前
初识Go语言
开发语言·后端·golang
spencer_tseng18 小时前
WeakAuras NES Script(lua)
lua·wow·nes·weakauras