Neovim Lua插件异步读文件遇EMFILE报错,如何设置并发限制?
解决Neovim异步读取大量文件时的EMFILE错误
问题分析
直接遍历文件列表调用异步read_file会一次性发起上千个异步文件操作,超出系统允许的同时打开文件数上限,触发EMFILE: too many open files错误。
你尝试的信号量方式之所以卡死,是因为while sema == 0 do end是同步阻塞循环,会彻底占用Neovim的事件循环,导致异步回调根本没机会执行,sema永远不会被加回去,陷入无限循环。
解决方案:异步任务队列+并发控制
我们可以维护一个待执行的任务队列,同时限制并发执行的异步任务数量。当有任务完成时,自动从队列中取出下一个任务执行,全程保持异步,不会阻塞事件循环。
改造文件读取函数(增强错误处理)
保留原异步逻辑,但替换assert为错误回调,避免崩溃:
local uv = vim.loop function utils.read_file(path, callback) uv.fs_open(path, "r", 438, function(err, fd) if err then return callback(nil, err) end uv.fs_fstat(fd, function(s_err, stat) if s_err then uv.fs_close(fd, function() end) return callback(nil, s_err) end uv.fs_read(fd, stat.size, 0, function(r_err, data) uv.fs_close(fd, function(c_err) -- 优先返回文件读取错误,关闭错误作为次要错误 if r_err then return callback(nil, r_err) end if c_err then return callback(data, c_err) end callback(data) end) end) end) end) end
实现并发任务调度器
function create_file_reader(concurrency) local queue = {} local running = 0 local function run_next() if running >= concurrency or #queue == 0 then return end running = running + 1 local task = table.remove(queue, 1) utils.read_file(task.path, function(data, err) -- 执行自定义文件处理逻辑 if err then vim.notify(string.format("读取文件失败 %s: %s", task.path, err), vim.log.levels.ERROR) else task.process_func(data) end running = running - 1 -- 任务完成后自动启动下一个任务 run_next() end) end return function(path, process_func) table.insert(queue, { path = path, process_func = process_func }) run_next() end end
使用方式
-- 设置并发数(比如10,可根据系统配置调整) local read_with_concurrency = create_file_reader(10) -- 遍历文件列表,将任务加入调度队列 for _, path in ipairs(paths) do read_with_concurrency(path, some_process_func) end
核心逻辑说明
create_file_reader返回一个调度函数,负责将任务加入队列并触发执行run_next函数控制并发上限:仅当当前运行任务数小于设定值且队列非空时,才启动新的异步读取- 每个任务完成(成功/失败)后,自动减少运行计数并启动下一个任务,全程不阻塞事件循环
内容的提问来源于stack exchange,提问作者Daniil Rozanov
相关产品推荐
相关产品推荐

