避免 Nodejs 上的竞争条件 Api

Avoiding race condition on 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
}

当然,我只想执行一次括号中的上述代码,因此我想避免最后两个用户在完全相同的时间发送他们的指示的竞争条件情况,并且他们都将设置他们的 属性 为真,然后在检查条件时,第一个用户可能会检查条件 - 结果为真,并且在将 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
    }
});

如果您只使用单个 NodeJS 进程,它是单线程的,因此竞争条件不会在单个帧中发生

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

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

参考资料如下:

但是,当

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

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

// 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>);
}