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
相关产品推荐
相关产品推荐

