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

JavaScript对象编辑互斥锁实现:异步文件读取对象同步问题

这个场景太常见了!在Node.js里用异步API处理文件时,多个回调同时修改同一个对象确实很容易导致数据竞态问题,比如覆盖值或者统计错误。下面给你几种实用的解决办法,从手动实现到现成库都有:

方法1:手动实现Promise队列(无外部依赖)

如果不想装额外的包,自己写几行代码就能实现一个简单的互斥锁核心逻辑——本质是把对目标对象的修改操作排队,前一个操作完成后再执行下一个,确保同一时间只有一个线程在修改对象。

const fs = require('fs').promises;

// 要汇总的目标对象
const summary = {};
// 维护一个锁Promise,初始状态为已完成
let mutex = Promise.resolve();

// 获取锁的函数,返回一个释放锁的方法
function acquireLock() {
  let resolveLock;
  // 创建下一个锁的Promise
  const nextLock = new Promise(resolve => {
    resolveLock = resolve;
  });
  // 保存当前的锁,等待它完成后再执行后续操作
  const currentLock = mutex;
  // 更新锁为下一个Promise
  mutex = nextLock;
  // 返回释放锁的函数
  return () => {
    currentLock.then(() => resolveLock());
  };
}

async function processFile(filePath) {
  const releaseLock = acquireLock();
  try {
    // 异步读取文件(这一步可以并行,不影响)
    const content = await fs.readFile(filePath, 'utf8');
    // 对summary的修改操作——这里是互斥的,同一时间只有一个函数能执行到这里
    const data = JSON.parse(content);
    summary[filePath] = data;
    console.log(`处理完成:${filePath}`);
  } catch (err) {
    console.error(`处理文件出错:${filePath}`, err);
  } finally {
    // 无论成功失败,一定要释放锁,不然后续操作会永远等待
    releaseLock();
  }
}

// 要处理的文件列表
const files = ['./file1.json', './file2.json', './file3.json'];

// 并行启动所有文件读取,但修改summary的操作会排队执行
Promise.all(files.map(file => processFile(file)))
  .then(() => {
    console.log('所有文件处理完成,汇总结果:', summary);
  });

核心逻辑是acquireLock函数:每次调用它都会把后续的修改操作加入队列,只有前一个操作调用releaseLock后,下一个操作才能进入临界区(修改summary的代码块)。

方法2:使用现成的异步锁库(推荐复杂场景)

如果觉得手动实现麻烦,或者需要更灵活的锁功能(比如多个锁、超时控制),可以用成熟的第三方库,比如async-lock。

首先安装依赖:

npm install async-lock

然后编写代码:

const fs = require('fs').promises;
const AsyncLock = require('async-lock');

const summary = {};
// 初始化锁,用一个唯一的key标识这个锁(比如'summary-mutex')
const lock = new AsyncLock();

async function processFile(filePath) {
  try {
    const content = await fs.readFile(filePath, 'utf8');
    // 用lock.acquire包裹需要互斥的代码块
    // 第一个参数是锁的key,第二个是要执行的异步函数
    await lock.acquire('summary-mutex', async () => {
      const data = JSON.parse(content);
      summary[filePath] = data;
      console.log(`处理完成:${filePath}`);
    });
  } catch (err) {
    console.error(`处理文件出错:${filePath}`, err);
  }
}

const files = ['./file1.json', './file2.json', './file3.json'];

Promise.all(files.map(file => processFile(file)))
  .then(() => {
    console.log('所有文件处理完成,汇总结果:', summary);
  });

这个库帮你封装了锁的队列管理、异常处理等细节,用起来非常省心,适合业务逻辑复杂的场景。

方法3:串行处理文件(最简单的方案)

如果你的文件数量不多,或者对处理速度要求不高,其实可以直接串行处理每个文件——上一个文件处理完再读下一个,从根源上避免并发修改的问题。

const fs = require('fs').promises;

const summary = {};

async function processFilesSequentially(files) {
  // 用for...of循环串行遍历文件
  for (const filePath of files) {
    try {
      const content = await fs.readFile(filePath, 'utf8');
      const data = JSON.parse(content);
      summary[filePath] = data;
      console.log(`处理完成:${filePath}`);
    } catch (err) {
      console.error(`处理文件出错:${filePath}`, err);
    }
  }
}

const files = ['./file1.json', './file2.json', './file3.json'];

processFilesSequentially(files)
  .then(() => {
    console.log('所有文件处理完成,汇总结果:', summary);
  });

这种方案没有任何锁的逻辑,因为根本不存在并发修改——每次只有一个文件的处理逻辑在执行,代码最简单易懂,唯一的缺点是处理速度会比并行读文件慢一些。


内容的提问来源于stack exchange,提问作者user3617487

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 09:19:37