首页 > 解决方案 > 避免 Nodejs Api 上的竞争条件

问题描述

我正在使用 Nodejs Api 服务器并面临一种特殊情况,即一群用户通过布尔指示通知我,并且只有当所有用户向我发送指示时,我才会调用一个方法来完成某些工作。

因此,对于示例,我创建了一组 5 个已连接用户,并等待他们的指示,该指示使用带有输入布尔值的 Http Post 消息发送。所以,在服务器上我持有一个对象如下 -

Group = {
   actionPerformed: false,
   userOne: false,
   userTwo: false,
   userThree: false,
   userFour: false,
   userFive: false
}

在收到来自以下任何用户的消息后,我会更新相关属性,比如说,对于 userOne,我将 Group.userOne 属性设置为 true。然后我检查是否所有其他用户已经发送了他们的指示,所以我执行以下测试 -

if (!Group.actionPerformed && 
    Group.userOne && 
    Group.userTwo && 
    Group.userOne && 
    Group.userThree && 
    Group.userFour && 
    Group.userFive) {
       Group.actionPerformed = true;
       //do something only once
}

当然,我只想在括号中执行上述代码一次,因此我想避免最后两个用户在完全相同的时间发送他们的指示的竞争条件情况,并且两者都将它们的属性设置为 true,然后作为检查条件,第一个用户可能会检查条件 - 结果为真,在将 actionPerformed 设置为真之前,可能会发生线程切换,第二个用户将测试条件也将结果为真,然后两个用户都会输入括号。

所以我的问题是,所描述的情况是否只能通过条件和 Group.actionPerformed = true 的原子操作来解决,或者,是否有另一种解决方案,也许更优雅?

更新 - 上面的代码在路由异步方法中执行 -

router.route('/')
    .get(passport.authenticate('jwt', { session: false }), async (req, res, next) => {
   ....
   if (!Group.actionPerformed && 
        Group.userOne && 
        Group.userTwo && 
        Group.userOne && 
        Group.userThree && 
        Group.userFour && 
        Group.userFive) {
           Group.actionPerformed = true;
           //do something only once
    }
});

标签: node.jsatomicrace-condition

解决方案


如果您只使用单个 NodeJS 进程,那么它是单线程的,因此竞态条件不会发生在单个 frame中。

另一种说法是:当代码执行以响应事件时,它不会被中断。

可以看到,如果你在你的服务器中进入一个无限循环,该进程将不会响应任何其他查询(JS中没有线程)。

以下是一些参考资料:

但是,您可以在以下情况下出现竞争条件

  • 运行多个 NodeJS 进程(在不同的机器上,或使用 NodeJScluster模块)。=> 在这种情况下,您不能将状态存储在 NodeJS 进程内存中

  • 在设置布尔值和检查它们是否全部设置之间执行任何异步工作(读取文件、异步/等待、网络......)。=> 改变这种行为

// This will always work, as js frames run to completion
async function toggle(userName) {
  Group[userName] = true;

  [...all the SYNCHRONOUS work you want...]

  if (!Group.actionPerformed && Group.userOne && ... && Group.userFive) {
       Group.actionPerformed = true;
       //do something only once
  }
}
// This may not work. A race condition is possible
async function toggle(userName) {
  Group[userName] = true;

  await database.get(somedocument); // this is asynchronous
  // the code below this line will not run in the same frame
  // so other javascript code may run in between

  if (!Group.actionPerformed && Group.userOne && ... && Group.userFive) {
       Group.actionPerformed = true;
       //do something only once
  }
}
// This may not work. A race condition is possible
async function toggle(userName) {
  Group[userName] = true;

  setTimeout(() => {
    if (!Group.actionPerformed && Group.userOne && ... && Group.userFive) {
       Group.actionPerformed = true;
       //do something only once
    }
  }, <any value>);
}

推荐阅读