JavaScript的TableManager类joinTable方法是否存在竞态条件?
问题解答
你的担忧完全成立,确实存在竞态条件
- 问题出在
createTable的异步逻辑上:第一次调用joinTable时,getTable同步执行未找到对应桌子,触发createTable,代码执行到await fetchTable(tableId)时会让出线程等待IO返回 - 此时如果第二个同ID的
joinTable请求进来,第一次的createTable还没执行到this.Tables.push(Table)的步骤,内存里还没有该桌子实例,所以第二个请求的getTable同样返回空,也会触发一次新的createTable调用 - 最终两次
fetchTable执行完成后,会往Tables数组里推入两个同ID的不同实例,完全符合你担心的情况
仅在createTable的push操作前加ID校验不能彻底解决问题
- 这种方式只能避免内存里最终出现重复的Table实例,但是还是会发起两次完全没必要的
fetchTable异步请求 - 如果你的
fetchTable逻辑本身包含建表、写数据库等副作用操作,还会导致数据库层面出现重复写入、主键冲突等问题,并没有从根源解决竞态
推荐解决方案
你可以给TableManager加一个pending状态的映射表,记录正在创建过程中的桌子ID对应的Promise,避免重复触发创建逻辑:
class TableManager { /** 存储已创建完成的Table对象数组 **/ protected Tables = []; /** 记录正在创建中的桌子ID对应的Promise **/ protected pendingCreations = new Map(); protected getTable(tableId) { return this.Tables.find(t => t.id === tableId) ?? null; } protected async createTable(tableId) { // 先判断有没有正在创建的同ID请求,有就直接返回该Promise if (this.pendingCreations.has(tableId)) { return this.pendingCreations.get(tableId); } const createPromise = (async () => { try { const Table = await fetchTable(tableId); // 额外加一次校验兜底防止极端情况 const existTable = this.getTable(tableId); if (existTable) return existTable; this.Tables.push(Table); return Table; } finally { // 无论创建成功失败都要清除pending状态 this.pendingCreations.delete(tableId); } })(); this.pendingCreations.set(tableId, createPromise); return createPromise; } protected async joinTable(user, tableId) { const Table = this.getTable(tableId) ?? await this.createTable(tableId); Table.addUser(user); } }
内容的提问来源于stack exchange,提问作者Adam Baranyai
相关产品推荐
相关产品推荐

