首页 / 技术文章地图 / 正文

【性能调优】协程 coroutine:调度原理与分帧实战

发布:2026-09-20 07:41 | 作者:996 技术组 | 3 阅读
完整课程入口:996 全套课程体系Lua 学习路径幂尔框架 mirs.cn

协程不是多线程

Lua 的协程(coroutine)是协作式"用户态线程":同一时刻只有一个协程在跑,切换点完全由你自己用 coroutine.yield 声明。它没有锁、没有竞态,是游戏脚本里做"分帧执行、异步流程"最安全的工具——这也是它和真线程的本质区别。

四个核心 API 构成全部生命周期:create 创建后返回一个 thread 对象但不执行;resume 启动或恢复它,一路跑到 yield 或结束;yield 挂起并把值带回给 resume;status 查看当前是 suspended、running 还是 dead。

lua
local co = coroutine.create(function(total)
    for i = 1, total do
        coroutine.yield(i)
    end
    return "done"
end)
release_print(coroutine.resume(co))  -- true  1
release_print(coroutine.resume(co))  -- true  2
release_print(coroutine.resume(co))  -- true  done 字符串

用它把重活切成小块

批量刷 5000 个怪物的数据初始化如果一口气做完,主循环会卡死一帧。用协程把工作切片,每帧只处理一批:

lua
local worker = coroutine.create(function(list)
    for i, cfg in ipairs(list) do
        initMonster(cfg)
        if i % 200 == 0 then coroutine.yield() end  -- 每 200 个让出一次
    end
end)

-- 挂进引擎的循环触发器里,每帧 resume 一次
local ok = coroutine.resume(worker, monsterList)
if coroutine.status(worker) == "dead" then
    release_print("初始化完成")
end

这就是最朴素的"分帧器"骨架:进度天然保存在协程内部,不需要额外的游标变量,中断与恢复都由调度器一句话完成。

异步流程与错误处理

协程的另一个高价值场景是把"回调地狱"拉直。把"等待玩家确认""等待服务器返回"封装成 yield 点,整段业务就能写成顺序代码,可读性远高于层层嵌套的回调。

错误处理要记住两条:协程内的 Lua 错误不会向外抛,而是通过 resume 的第一个返回值(false)和错误消息带回,所以每次 resume 都要检查 ok;需要完整栈信息时,在协程体内用 xpcall(主逻辑, debug.traceback) 包一层,resume 拿到的错误串就带着出错位置,排查联动功能时能省一半时间。

最后一个实用细节:协程对象本身有开销,但很小,按任务粒度创建即可;真正要避免的是在 yield 之后把宿主数据悄悄改掉——恢复点之间数据结构发生了变化,是协程逻辑出 bug 的最大来源,写代码时保持"yield 前后假设世界会变"的警惕即可。

分帧推进

setontimerex(id, tick)

添加全局定时器,协程分帧最常用的驱动器

参数类型说明
idinteger定时器ID(必填参数)
tickinteger执行间隔,秒(必填参数)
lua
setontimerex(23, 5)

setofftimerex(id)

移除全局定时器,协程跑完必须摘掉,否则下一帧继续进回调

参数类型说明
idinteger定时器ID(必填参数)

ontimerexX(empty)

全局定时器触发(X=触发器ID),在触发里推进协程,注意单帧预算

参数类型说明
emptyempty无参数

延时回调

delaygoto(actor, time, func, del)

延时跳转,需要等待一段时间的协程用延时回调衔接

参数类型说明
actorobject玩家对象(必填参数)
timeinteger时间(毫秒)(必填参数)
funcstring触发函数(必填参数)
delinteger换地图是否删除此延时(0或为空时=不删除 1=删除)(必填参数)

cleardelaygoto(actor, func)

删除延迟,协程被取消时同步清掉挂着的延时

参数类型说明
actorobject玩家对象(必填参数)
funcstring需要删除的延时函数不填为清除全部(必填参数)

耗时

printusetime(play, on, off)

打印脚本总耗时(微秒),验证分帧是否真的把单帧耗时压下来了

参数类型说明
playobject玩家对象
oninteger1=开始技术2=结束计时并打印耗时
offinteger2=结束计时
作者履历与出处
本文由 996 技术组基于 996 引擎官方知识库与浮生梦老师课程体系整理,讲解体系出自多年商业端开发生产一线。作者团队长期从事传奇类引擎 Lua 后端逻辑、客户端界面与版本交付,内容以官方知识库与真实项目为出处,按版本持续修订。
© 威海旷世互娱 · 返回文章地图 · 课程体系 · 幂尔框架