我应该关注这里的竞争条件吗?

Should I be concerned about a race condition here?

我在 javascript 中有一个 class,结构如下:

class TableManager {
      /** an array containing Table objects **/
      protected Tables = [];

      protected getTable(tableId) {
         // iterates over this.Tables, and searches for a table with a specific id: if found, it returns the table object, otherwise it returns null
      }

      protected async createTable(tableId) {
        const Table = await fetchTable(tableId); /** performs an asynchronous operation, that creates a Table object by performing a select operation on the database **/
 
        this.Tables.push(Table);
        return Table;
      }

      protected async joinTable(user, tableId) {
          const Table = this.getTable(tableId) ?? await this.createTable(tableId);

          Table.addUser(user);
      }
}

这个 class 背后的想法是它将通过套接字接收命令。例如,它可能收到 joinTable 命令,在这种情况下,它应该首先检查正在加入的 table 是否已经存在于内存中:如果存在,它将把用户添加到那个table,否则会创建table,存入内存,将用户添加到table。

我有点担心,如果在短时间内进行两次 joinTable() 调用,这可能会导致竞争条件,在这种情况下,将创建 tables两次,并作为两个单独的 table 实例存储在内存中。我对此感到害怕是对的吗?如果是,是否会在将 table 添加到 createTable 函数中的数组之前检查它是否存在,解决此竞争条件?

你的担心是对的。这个想法是交易,并确保在给定时间只有一个交易 运行。在 Nodejs 中,您可以使用 Mutex 来实现它。阅读更多:https://www.nodejsdesignpatterns.com/blog/node-js-race-conditions/.

I am a bit concerned, that this could result in a race condition, if two joinTable() calls are made in a short amount of time, in which case the tables will be created twice, and stored in memory as two separate table instances. Am I right to be affraid about this?

只要您 await 每次调用(或正确链接),这应该不是问题。也就是说,只要操作是顺序的,就没有问题。如果你允许承诺同时解决(比如 Promise.all)那么是的,就像现在一样,会有竞争条件。

If yes, would checking if the table exists before adding it to the array in the createTable function, solve this race condition?

据我了解,不,它仍然会造成竞争条件。第一个函数调用将进行检查,发现 table 不存在并继续将查询发送到您的服务器以创建新条目。第二个函数调用也会进行检查,但由于它不等待上一个请求,因此检查可能在第一个请求完成之前发生(这是您的竞争条件)。这意味着可以发送另一个请求来创建另一个 table.

您可以做的是将您的条目存储为承诺。我会为此使用 Map

  protected Tables = new Map();

  protected getTable(tableId) {
    let Table = this.Tables.get(tableId);
    if(!Table){
      Table = fetchTable(tableId);
      this.Tables.set(tableId, Table);
    }
    return Table;
  }

这样,joinTable 可以改为执行 getTable,这也会创建 Table(如果不存在)。如果 Table 正在创建,它将接受承诺并且不会以这种方式重复。

最终,无论是否在服务器上创建任何实体,都需要在服务器上...在服务器上进行管理。否则,您可能会冒多个客户端(甚至客户端重新启动)创建这些副本的风险。