javascript - NodeJS函数被socketio事件中断

标签 javascript arrays node.js multithreading socket.io

我在我的nodejs游戏服务器中看到一些奇怪的行为,其中似乎存在并发性。这很奇怪,因为Nodejs应该在一个线程中运行,因为它不使用任何并发性。问题是我有一个使用setImmediate()反复调用的更新函数。在此函数中,我在两个地方使用数组。但是,在触发“断开连接”事件时(也就是客户端与服务器断开连接时),也会修改此同一阵列。因此,发生了这样的情况:当时间对齐时,在更新功能中访问数组的第一个位置之后触发断开事件,但在第二个位置之前触发断开事件,从而修改了数组,因此当尝试执行该操作时,服务器崩溃在第二位被访问。

这是一些可以使这张图片清晰的代码:

function update(){
    for(var i = 0; i < gameWorlds.length; i++){
        gameWorlds[i].update();
        console.log("GAMEWORLDS LENGTH BEFORE: " + gameWorlds.length);
        NetworkManager.sendToClient(gameWorlds[i].id, "gameupdate", gameWorlds[i].getState());
        console.log("GAMEWORLDS LENGTH AFTER: " + gameWorlds.length);
        gameWorlds[i].clearGameState();
    }
}

setImmediate(update);

//in the NetworkManager module, the disconnect event handler:
socket.on("disconnect", function(){
    for(var a = 0; a < sockets.length; a++){
       if(sockets[a].id === socket.id){
                sockets.splice(a, 1);
             }
        }
        listenerFunction("disconnect", socket.id);
        console.log("Client " + socket.id + " DISCONNECTED!");
});

//also in the NetworkManager module, the sendToClient function:
function sendToClient(clientId, messageName, data){
    for(var i = 0; i < sockets.length; i++){
        if(sockets[i].id === clientId){
            sockets[i].emit(messageName, data);
        }
    }
}

//in the main module (the same one as the update function), the listener      
//function that's called in the disconnect event handler:
function networkEventsListener(eventType, eventObject){
   if(eventType === "disconnect"){
       for(var i = 0; i < gameWorlds.length; i++){
            if(gameWorlds[i].id === eventObject){
                gameWorlds.splice(i, 1); 
                console.log("GAME WORLD DELETED");
            }
        }
   }
}

现在,我为客户端断开连接(其中删除数组中的元素)时设置了一个socketio事件监听器。在访问数组的第一个位置和第二个位置之间发生此事件时(如上所示),我的服务器崩溃了。正在使用线程,或者停止了我的功能以使事件处理程序执行,然后恢复我的功能。无论哪种方式,我都不希望这种情况发生。谢谢!

编辑1:我编辑了代码,以将我的代码中包含控制台日志。我之所以说我的循环正在中断,是因为第二个控制台日志的输出长度为0,而第一个控制台日志的输出长度大于0。此外,在断开事件处理程序中还有另一个控制台日志,在两个控制台之间的FIRES登录我的更新功能。这意味着我的功能正在被中断。

编辑2:非常感谢您的所有答复。我认为在以下方面有些困惑:
1.没有人确认控制台日志的显示方式。在之前的编辑中,我更改了代码以反射(reflect)如何记录日志以查看问题。问题是在断开事件处理程序中,我有一个控制台日志,该日志正在循环中的两个控制台日志之间发生。 IE。断开事件处理程序在循环中到达第二个控制台日志之前执行。除非我对控制台日志功能的实现感到困惑,否则日志应该以正确的顺序发生(也就是说,循环中的两个控制台日志应始终出现在程序其余部分中任何其他控制台日志之前,正如大多数人所说的那样,它具有ASYNC的性质。)事实并非如此,这使我相信某些奇怪的事情正在发生。
2.循环内的所有代码均未更改数组。在很多答复中,您都假定有一些代码实际上在循环内修改了数组,而事实并非如此。唯一修改数组的代码是循环的代码OUTISDE,这就是为什么奇怪的是,即使在DOESN之间的代码,访问数组的循环的第一部分也不会崩溃,而第二部分会崩溃。 '改变阵列。

编辑3:好的,所以很多答复都要求输入COMPLETE代码。我已经用所有相关的REAL代码更新了代码。

最佳答案

node.js中的Javascript是单线程的。用Javascript执行的给定线程不会被socket.io断开事件中断。从物理上讲这不可能发生。 node.js是事件驱动的。当断开事件发生时,一个事件将被放入Javascript事件队列中,只有当您当前的执行线程完成后,Javascript才会从事件队列中捕获下一个事件并调用与之关联的回调。

您不会显示足够多的真实代码来确定,但是可能发生的是,如果您有异步操作,那么当您启动异步操作并注册一个回调以完成操作时,您将完成该Javascript线程。执行,这只是看接下来发生哪个异步事件(此特定异步操作的完成或来自socket.io断开连接的断开事件)的竞赛。那是不确定的,并且这些事件可以以任何顺序发生。因此,如果您有问题的代码中包含异步代码,则可以在该代码等待异步事件完成时处理断开事件。

这是在node.js编程中必须注意的竞争条件类型。每当您的逻辑变得异步时,在代码等待表示操作已完成的异步回调时,node.js中便可以处理其他事情。

确切地做些什么完全取决于实际情况,我们需要查看并理解您的真实代码(而非伪代码)才能知道最适合您的选项。仅供引用,这就是如果您向我们展示您的真实代码,而不仅仅是伪代码,我们将始终为您提供更好的帮助的原因之一。

当对共享数据结构进行异步操作时,可以使用以下一些技术,这些技术可能会被其他异步代码更改:

  • 制作要处理的数据的副本,因此没有其他代码可以访问您的副本,因此不能被任何其他代码修改。这可能是在复制数组,也可能只是使用闭包在本地捕获索引,因此该索引不会受到其他代码的影响。
  • 使用标志来保护正在被修改的数据结构,并训练所有其他代码以遵守该标志。具体如何执行取决于具体数据。我在Raspberry Pi node.js应用程序中有代码,该应用程序会定期将数据保存到磁盘,并且处于竞争状态,在我处于使用异步I/O进行写入的过程中,其他事件驱动的代码可能希望更新该数据它到磁盘。因为数据可能很大,而系统的内存却没有那么大,所以我无法像第一点所建议的那样复制数据。因此,我使用了一个标志来表明我正在将数据写入磁盘,并且任何希望在设置该标志时修改数据的代码都会将其操作添加到队列中,而不是直接修改数据。然后,当我完成将数据写入磁盘后,代码将检查队列,以查看是否需要执行任何挂起的操作来修改数据。并且,由于数据是由对象表示的,并且对数据的所有操作都由对象上的方法执行,因此使用数据或尝试修改数据对代码都是透明的。
  • 将数据放入具有并发功能和内置控件的实际数据库中,以便可以对数据进行原子更改,或者可以在短时间内锁定数据,或者可以安全方式获取或更新数据。数据库有很多可能的策略来处理这种情况,因为数据库经常会发生这种情况。
  • 使对数据的所有访问都是异步的,因此,如果在修改数据的过程中还有其他异步操作,则其他“不安全”的访问数据的尝试可能会“阻塞”,直到完成原始操作为止。这是数据库使用的一种技术。当然,您确实需要当心死锁或未清除标志或锁的错误路径。


  • 根据您发布更多代码的一些新评论:

    这段代码是错误的:
    //in the main module (the same one as the update function), the listener      
    //function that's called in the disconnect event handler:
    function networkEventsListener(eventType, eventObject){
       if(eventType === "disconnect"){
           for(var i = 0; i < gameWorlds.length; i++){
                if(gameWorlds[i].id === eventObject){
                    gameWorlds.splice(i, 1); 
                    console.log("GAME WORLD DELETED");
                }
            }
       }
    }
    

    当您在要迭代的数组的.splice()循环的中间调用for时,它将导致您错过要迭代的数组中的项目。我不知道这是否与您的问题有关,但这是错误的。避免此问题的一种简单方法是向后迭代数组。然后,调用.splice()不会影响尚未迭代的任何数组元素的位置,并且您不会错过任何数组中的元素。
    for处理程序中的disconnect循环中存在相同的问题。如果您只希望在迭代中匹配一个数组元素,则可以在break之后立即添加splice(),这样可以避免此问题,并且您不必向后迭代。

    关于javascript - NodeJS函数被socketio事件中断,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39027236/

    相关文章:

    javascript - 如何将名称属性添加到动态生成的 <tr>

    javascript - 从不同文件导入的最佳方法

    arrays - 如何过滤嵌套数组作为响应?

    python - 将元组列表混合并迭代到文本模板中

    php - 将 php 数组插入 mySql

    javascript - 遗漏或尝试后终于

    javascript - Node Express 使用 JSON 作为 API 响应发送图像文件

    javascript - 在服务器上引用 Node.js

    javascript - 模拟服务 worker /节点不工作,我不明白为什么

    javascript - 带有数字填充的 CSS 计数器