我在我的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中便可以处理其他事情。
确切地做些什么完全取决于实际情况,我们需要查看并理解您的真实代码(而非伪代码)才能知道最适合您的选项。仅供引用,这就是如果您向我们展示您的真实代码,而不仅仅是伪代码,我们将始终为您提供更好的帮助的原因之一。
当对共享数据结构进行异步操作时,可以使用以下一些技术,这些技术可能会被其他异步代码更改:
根据您发布更多代码的一些新评论:
这段代码是错误的:
//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/