我在javascript中有一个类,结构如下:
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);
}
}
这个类背后的想法是,它将通过套接字接收命令。例如,它可能会收到joinTable
命令,在这种情况下,它应该首先检查正在连接的表是否已存在于内存中:如果存在,它将用户添加到该表中,否则,它将创建表,将其存储在内存中,然后将用户添加到表中。
我有点担心,如果在短时间内进行两次joinTable()
调用,这可能会导致竞争条件,在这种情况下,表将被创建两次,并作为两个单独的表实例存储在内存中。我害怕这个是对的吗?如果是,在将表添加到createTable
函数中的数组之前检查表是否存在,是否可以解决此争用条件?
你的担忧是对的。 这个想法是事务,并确保在给定时间只有一个事务在运行。在 Nodejs 中,你可以使用 Mutex 来实现这一点。阅读更多:https://www.nodejsdesignpatterns.com/blog/node-js-race-conditions/。
我有点担心,这可能会导致竞争条件,如果 在短时间内进行了两次 joinTable() 调用,其中 在这种情况下,表将被创建两次,并作为两个存储在内存中 单独的表实例。我害怕这个是对的吗?
只要您await
每个调用(或正确链接它),这应该不是问题。也就是说,只要操作是顺序的,就不会有问题。如果您允许承诺同时解决(就像Promise.all
一样),那么是的,就像现在一样,将存在竞争条件。
如果是,在将表添加到数组之前检查该表是否存在 创建表函数,解决这个竞争条件?
据我了解,不,它仍然会产生竞争条件。第一个函数调用将执行检查,查看表不存在并继续将查询发送到服务器以创建新条目。第二个函数调用也会执行检查,但由于它不等待上一个请求,因此检查可能会在第一个请求完成之前发生(这是您的争用条件)。这意味着可以发送另一个请求来创建另一个表。
您可以做的是将您的条目存储为承诺。我会为此使用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
,它将获得承诺,并且不会以这种方式进行重复。
最终,在服务器上创建与否任何实体都需要在那里进行管理......在服务器上。否则,您将面临多个客户端(甚至客户端重新启动)创建这些重复项的风险。