在Lua中注册回调:实现Lua函数定时调用的技术咨询
问题背景与需求
架构说明(欢迎提出架构批评)
- 现有大型C++程序,承载大量任务
- 引入Lua实现脚本化功能,大量底层命令由Lua代码片段处理
- 每个
command关联一段Lua代码片段,执行command时触发对应片段执行 - Lua代码片段可回调底层C++函数,此逻辑不影响核心需求
- Lua代码片段在
command启动后立即退出 command实际在外部执行(如视频播放/录制启停、倒带等),程序需主动轮询其状态,无回调、join机制
核心问题
部分Lua代码片段场景下,需要调度特定Lua函数的定时调用:
- 单次调用:处理超时逻辑
- 循环调用:处理外部
command的状态轮询
计划通过C启动线程实现定时,但不清楚如何传递Lua函数信息给C,以及如何在C++中调用该Lua函数
当前Lua类代码(未实现定时回调)
static inline bool is_whitespace(const std::string& s) { return std::all_of(s.begin(), s.end(), isspace); } class Lua { lua_State *state; std::map<std::string, int> fragments; void push(std::string const& str) { lua_pushstring(state, str.c_str()); } void push(char const* str) { lua_pushstring(state, str); } void push(bool value) { lua_pushboolean(state, value); } void push(uint32_t value) { lua_pushinteger(state, value); } void push(int value) { lua_pushinteger(state, value); } void push(double value) { lua_pushnumber(state, value); } public: void push(std::nullptr_t) = delete; Lua() : state(luaL_newstate()) { luaL_openlibs(state); } void register_c_func(const char *name, lua_CFunction func) { lua_register(state, name, func); } bool is_snippet(const std::string &name) { return fragments.count(name); } int register_snippet(const char *name, const char *fragment) { if (is_snippet(name)) { SPDLOG_ERROR("lua::register_snippet({}): ERROR name already present", name); return LUA_NOREF; } if (!is_whitespace(fragment)) { auto ret = luaL_loadstring(state, fragment); if (ret != LUA_OK) { SPDLOG_ERROR("lua::register_snippet({}): Lua registration failed ({})"); } else { auto r = luaL_ref(state, LUA_REGISTRYINDEX); SPDLOG_TRACE("lua::register_snippet({}, {}): {}", name, fragment, r); fragments[name] = r; return r; } } else { SPDLOG_WARN("lua::register_snippet({}): has empty Lua fragment", name); } return LUA_NOREF; } template<typename... Args> bool exec(const std::string &name, Args... args){ if (is_snippet(name)) { lua_rawgeti(state, LUA_REGISTRYINDEX, fragments.at(name)); ((push(std::forward<Args>(args))), ...); int nargs = sizeof ...(Args); SPDLOG_TRACE("lua::exec({}, {}): {}", name, fragments.at(name), nargs); lua_pcall(state, nargs, LUA_MULTRET, 0); return true; } else { SPDLOG_WARN("lua::exec({}): has no Lua fragment", name); return false; } } void exec(const char* fragment) { SPDLOG_TRACE("lua::exec(): '{}'", fragment); auto ret = luaL_dostring(state, fragment); if (ret) SPDLOG_ERROR("lua::exec(): ERROR {}", ret); } }; extern Lua master_lua;
注:Lua初始化、C++函数注册及代码片段注册不在此代码范围内
解决方案
核心思路
利用Lua注册表存储定时函数的引用(整数索引),C++侧维护定时任务队列,通过线程触发Lua函数调用。关键要点:
- Lua函数可通过
luaL_ref存入注册表,得到的整数索引可安全传递给C++ - Lua状态机非线程安全,所有Lua API调用必须在同一线程,或通过互斥锁同步
- 定时任务需包含函数索引、延迟/间隔、循环标记,以及可选的取消标识
步骤1:扩展Lua类,添加线程安全调用支持
给Lua类添加状态机访问、互斥锁保护及注册表函数调用逻辑:
class Lua { // ... 原有代码 ... // 添加互斥锁保护Lua状态机访问 std::mutex mtx; public: // ... 原有代码 ... // 获取Lua状态机(需配合互斥锁使用) lua_State* get_state() { return state; } // 获取互斥锁,用于线程安全访问 std::mutex& get_mutex() { return mtx; } // 封装调用注册表中函数的方法 bool call_registry_func(int ref, int nargs = 0, int nresults = LUA_MULTRET) { std::lock_guard<std::mutex> lock(mtx); lua_rawgeti(state, LUA_REGISTRYINDEX, ref); int ret = lua_pcall(state, nargs, nresults, 0); if (ret != LUA_OK) { SPDLOG_ERROR("Lua function call failed: {}", lua_tostring(state, -1)); lua_pop(state, 1); return false; } lua_pop(state, nresults); return true; } // 释放注册表中的函数引用 void unref(int ref) { std::lock_guard<std::mutex> lock(mtx); luaL_unref(state, LUA_REGISTRYINDEX, ref); } };
步骤2:定义定时任务结构
在C++中定义存储定时任务信息的结构体:
#include <chrono> #include <atomic> struct ScheduledTask { int lua_func_ref; // Lua注册表中的函数索引 std::chrono::milliseconds delay; // 首次执行延迟 std::chrono::milliseconds interval; // 循环执行间隔(单次任务设为0) std::atomic<bool> canceled; // 任务取消标记 std::chrono::steady_clock::time_point next_run; // 下次执行时间 ScheduledTask(int ref, std::chrono::milliseconds d, std::chrono::milliseconds i) : lua_func_ref(ref), delay(d), interval(i), canceled(false) { next_run = std::chrono::steady_clock::now() + delay; } };
步骤3:实现定时任务调度器
创建单例调度器,维护任务队列并启动处理线程:
#include <queue> #include <thread> #include <condition_variable> class TaskScheduler { private: std::priority_queue< ScheduledTask*, std::vector<ScheduledTask*>, // 按next_run升序排列,优先执行最早的任务 std::function<bool(ScheduledTask*, ScheduledTask*)> > task_queue{ [](ScheduledTask* a, ScheduledTask* b) { return a->next_run > b->next_run; } }; std::mutex queue_mtx; std::condition_variable cv; std::atomic<bool> running{true}; std::thread worker_thread; void worker_loop() { while (running) { std::unique_lock<std::mutex> lock(queue_mtx); cv.wait(lock, [this]() { return !task_queue.empty() || !running; }); if (!running && task_queue.empty()) break; auto now = std::chrono::steady_clock::now(); auto task = task_queue.top(); if (task->next_run > now) { cv.wait_until(lock, task->next_run); continue; } task_queue.pop(); lock.unlock(); if (task->canceled) { master_lua.unref(task->lua_func_ref); delete task; continue; } master_lua.call_registry_func(task->lua_func_ref); if (task->interval.count() > 0 && !task->canceled) { task->next_run = std::chrono::steady_clock::now() + task->interval; std::lock_guard<std::mutex> lock2(queue_mtx); task_queue.push(task); cv.notify_one(); } else { master_lua.unref(task->lua_func_ref); delete task; } } } public: TaskScheduler() { worker_thread = std::thread(&TaskScheduler::worker_loop, this); } ~TaskScheduler() { running = false; cv.notify_all(); if (worker_thread.joinable()) { worker_thread.join(); } std::lock_guard<std::mutex> lock(queue_mtx); while (!task_queue.empty()) { auto task = task_queue.top(); task_queue.pop(); master_lua.unref(task->lua_func_ref); delete task; } } // 注册定时任务:返回任务指针用于取消 ScheduledTask* schedule_task(int lua_func_ref, std::chrono::milliseconds delay, std::chrono::milliseconds interval = std::chrono::milliseconds(0)) { auto task = new ScheduledTask(lua_func_ref, delay, interval); std::lock_guard<std::mutex> lock(queue_mtx); task_queue.push(task); cv.notify_one(); return task; } // 取消定时任务 void cancel_task(ScheduledTask* task) { if (task) { task->canceled = true; cv.notify_one(); } } }; extern TaskScheduler task_scheduler;
步骤4:注册C++函数到Lua,暴露调度接口
实现Lua可调用的C函数,用于注册和取消定时任务:
// Lua调用此函数注册定时任务 // 参数:func(要定时调用的Lua函数), delay_ms(延迟毫秒数), interval_ms(循环间隔,可选,0为单次) static int lua_schedule_task(lua_State* L) { if (!lua_isfunction(L, 1)) { lua_pushstring(L, "first argument must be a function"); lua_error(L); } if (!lua_isnumber(L, 2)) { lua_pushstring(L, "second argument must be a number (delay ms)"); lua_error(L); } int interval_ms = lua_isnumber(L, 3) ? lua_tointeger(L, 3) : 0; lua_pushvalue(L, 1); int ref = luaL_ref(L, LUA_REGISTRYINDEX); auto delay = std::chrono::milliseconds(lua_tointeger(L, 2)); auto interval = std::chrono::milliseconds(interval_ms); auto task = task_scheduler.schedule_task(ref, delay, interval); lua_pushlightuserdata(L, task); return 1; } // Lua调用此函数取消定时任务 // 参数:task(之前返回的任务指针) static int lua_cancel_task(lua_State* L) { auto task = static_cast<ScheduledTask*>(lua_touserdata(L, 1)); if (task) { task_scheduler.cancel_task(task); } return 0; } // 在Lua初始化时注册这两个函数 // master_lua.register_c_func("schedule_task", lua_schedule_task); // master_lua.register_c_func("cancel_task", lua_cancel_task);
步骤5:Lua侧使用示例
在Lua代码片段中调用注册的函数实现定时逻辑:
-- 单次定时调用:1秒后执行超时处理 local timeout_task = schedule_task(function() print("Timeout triggered, stopping command...") stop_command() -- 调用C++函数停止command end, 1000) -- 循环轮询:每500毫秒检查command状态 local poll_task = schedule_task(function() local status = check_command_status() -- 调用C++函数获取状态 print("Command status:", status) if status == "finished" then cancel_task(poll_task) -- 任务完成,取消轮询 print("Command finished, stopping poll") end end, 0, 500)
架构补充建议
- 线程安全:所有Lua状态机操作必须通过互斥锁保护,避免多线程同时访问导致崩溃
- 资源清理:当
command结束时,必须取消所有关联的定时任务,避免无效调用和内存泄漏 - 错误处理:Lua函数调用失败时,需记录错误日志并清理相关资源
- 性能优化:如果定时任务数量较多,可考虑使用基于IO复用的定时器替代线程轮询
内容的提问来源于stack exchange,提问作者ZioByte
相关产品推荐
相关产品推荐

