You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 03:12:37