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

待续

相关推荐
这是谁的博客?1 小时前
Python 异步编程核心原理与实践深度解析
java·网络·python·协程·asyncio·异步编程
李燚2 小时前
ReAct 循环的 50 行 Go 实现,逐行拆解
javascript·人工智能·react.js·golang·aigc·agent
念何架构之路4 小时前
Go pprof性能剖析
开发语言·后端·golang
姚不倒4 小时前
Go语言实战:构建一个安全的计算器服务(接口、错误处理与Panic恢复)
云原生·golang
XMYX-019 小时前
37 - Go env 环境变量:配置管理与运行时控制
开发语言·golang
上海合宙LuatOS1 天前
Air8000多网通信- RNDIS/ECM
物联网·lua·嵌入式开发·多网通信
姚不倒1 天前
Go 进阶实战:实现泛型数据验证器
云原生·golang
XMYX-01 天前
36 - Go exec 执行命令
开发语言·golang
lolo大魔王1 天前
Go 语言 HTTP 协议与 RESTful API 实训全解(理论 + 实战 + 规范)
http·golang·restful
一只小逸白1 天前
LeetCode Go 常用函数速查表
linux·leetcode·golang