使用 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 对象,因此暂时无需求多线程中的协程间的通信需求

待续

相关推荐
ttwuai8 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
星空露珠11 小时前
28种颜色对应名称,
开发语言·数据库·算法·游戏·lua
布朗克16812 小时前
Go 入门到精通-31-编码与序列化
开发语言·qt·golang·编码与序列化
microrain16 小时前
从硬编码到热插拔:SagooIoT插件架构如何打破物联网平台的扩展瓶颈
物联网·golang·开源·sagooiot
geovindu17 小时前
go:Backtracking Algorithm
开发语言·后端·算法·golang·回溯算法
qq_4523962317 小时前
第六篇:《并发编程核心:Goroutine 与 Channel》
golang
llwszx17 小时前
【Java/Go后端手撸原生Agent(第五篇):多工具并行调用 + BashTool执行引擎 + Judge证据链升级】
java·后端·golang·状态机·pydantic·agnet·llm-as-judge
FfHUCisI17 小时前
Go语言程序结构 —— 变量、声明与零值机制
golang
超人不会飞_Jay18 小时前
Go课程2
开发语言·后端·golang
晴空了无痕1 天前
从 Go 基础到 K8s:一条可落地的 Go 服务端成长路线
开发语言·后端·golang·kubernetes