网络工作者突然终止

Posted

技术标签:

【中文标题】网络工作者突然终止【英文标题】:Web workers terminating abruptly 【发布时间】:2016-09-03 03:01:19 【问题描述】:

我在 chrome 上启动了一个 web worker,它有一个简单的函数,可以使用 setTimeout 重复调用。令人惊讶的是,在该函数被调用了大约 1000 次后,Web Worker 终止了。谁能解释为什么?我猜 chrome 正在做一些优化。

webworker.js

function hi() 
    postMessage('1');
    setTimeout(hi, 1);

hi();

ma​​in.js

var blob = new Blob([code]);
var blobURL = window.URL.createObjectURL(blob);
var worker = new Worker(blobURL);
worker.onmessage = function(data) 
    console.log(data.data); // gets called around 1000 times and done
;

编辑: 在小提琴中转载: http://jsfiddle.net/meovfpv3/1/ onmessage 回调停止触发似乎需要任意长的时间,最快几秒,最长 +5 分钟

【问题讨论】:

当然我们可以解释原因,但不要让我们猜测。您必须展示一个可验证的最小示例,以便我们对其进行测试。 @IsmaelMiguel 我已经用 worker.js 代码更新了问题。基本上我用一个字符串做一个 blob 并把它交给工人 我遇到了同样的问题。我发现工人不会终止(用console.log 测试),onmessage 回调只是在某个时候停止被触发。非常奇怪且不可接受的浏览器行为! 桌子翻转 可能是消息队列已满或类似情况? 或者也许你的工人正在被垃圾收集,如果你不保留对他们的引用,这将会发生(我认为)。 【参考方案1】:

这是我对正在发生的事情的最佳猜测。通过每 1 毫秒从 Web Worker 发布一条消息,您要求主线程在 1 毫秒内处理每条发布的消息。

如果主线程无法在 1 毫秒内处理消息,即使它还没有完成对最后一条消息的处理,您仍然会向它发送一条新消息。我想这会将它放入等待处理的消息队列中。

现在,由于您从 Web Worker 发送消息的速度快于处理它们的速度,因此这个未处理消息的队列将会越来越大。在某个时候,Chrome 会举手说“队列中有太多消息”,而不是将新消息排队等待处理,而是丢弃它们。

这就是为什么如果您在超时中使用一个合理的数字(例如 100 毫秒),则在发送下一条消息之前,该消息有足够的时间进行处理,并且未处理的消息不会出现问题。


我创建了一个 jsFiddle,worker 向主线程发送消息,主线程将消息发送回 worker。如果在发送下一条消息之前该过程没有发生,则两个线程中的计数器将不匹配,并且 Web Worker 将终止。

http://jsfiddle.net/meovfpv3/3/

您可以看到,合理的 setTimeout 为 100 毫秒,所有消息在下一条消息发生之前都有足够的时间进行处理。

当您将 setTimeout 降低到 1 毫秒时,消息链在发送下一条消息之前没有时间完成,并且每个线程中的计数器最终会变得不同步,从而触发 if 子句并终止 Web Worker。


解决此问题的一种方法是,不要盲目地每 1ms 发布一条消息,无论最后一条消息是否已处理,只有在收到主线程返回的消息后才发布一条新消息。这意味着您只能以主线程可以处理它们的速度发布消息。


为了完整起见,这里是JSFiddle code的副本:

工人:

  var counter2 = 0;
  var rcvd = true;
  function hi() 
    counter2++;
    console.log("")
    console.log("postMessage", counter2)
    postMessage(counter2);
    if (!rcvd) 
        self.close();
      console.log("No message received");
    
    rcvd = false;
    setTimeout(hi, 1);
  
  hi();
  onmessage = function(e) 
    rcvd = true;
    console.log("secondMessage", e.data);
  

主要:

var ww = document.querySelector('script[type="text/ww"]'),
    code = ww.textContent,
    blob = new Blob([code], type: 'text/javascript'),
    blobUrl = URL.createObjectURL(blob),
    worker = new Worker(blobUrl),
    counter = 0;

worker.onmessage = function(e) 
    counter++;
  console.log("onmessage:", counter);
  worker.postMessage(e.data);

【讨论】:

但是 setTimeout 实际上不会每毫秒执行一次;充其量它会每 10-20 毫秒执行一次。 @torazaburo 没错,但setTimeout 仍会尽可能快地执行,这比它创建的消息的处理速度要快。 谢谢 Max,有趣的理论。但是,我的原始代码(与 OP 不同)使用了 85 毫秒的超时,因此 似乎 揭穿了这个理论。我什至不确定我添加的原始 jsfiddle 是否公正,因为问题似乎确实可以可靠地重现。不幸的是,由于存在机密代码,我无法在小提琴中提供我的整个用例。看起来你无论如何都会得到赏金(A表示努力!),除非其他人能指出问题所在。我怀疑这个问题是 Chrome v50.2xx 的新问题,与 Web Worker 优化有关 @AnsonKao 非常有趣。我想如果您对主线程中的消息处理功能进行时间​​测量,它会在 85 毫秒内始终处理消息,对吗? (如果超过平均水平,队列仍然会增长。) 我倾向于怀疑 Chrome 是否会因为 1000 条待处理消息而感到不堪重负,以至于它会举手杀死工人或丢弃消息。【参考方案2】:

首先,我无法解释一些观察结果,但很有趣,可能对某人有启发:

@Anson - 如果我将您的 jsFiddle 代码放入 Codepen(仍在 Chrome 中),则没有问题。 onmessage 回调一直在工作!

然后回到 jsFiddle... 它甚至无法将 setTimeout 更改为像 10 秒这样的长间隔,所以这不是工作人员发布消息的次数,而是 onmessage 回调之前的时间停止射击——这有很大的差异。

然后我找到了一些方法来让onmessage处理程序在这个特定的例子中保持活跃:

html 中添加一个按钮/链接和一个处理程序(我使用 jQuery),该处理程序将在单击时终止工作程序。只需添加此代码即可修复它。 $("#stop").on("click",function(e)e.preventDefault();worker.terminate();); 只需在定义onmessage 后添加console.log(worker)。 受related question 中发布的答案启发,您也可以在定义onmessage 后简单地添加window.worker = worker

在所有情况下再次提及worker 似乎可以让它保持活力。

【讨论】:

当然可以。你有一个工人的参考。所以不会被垃圾回收。【参考方案3】:

您是否尝试每 1 毫秒发布一次消息?那么你可能打算使用setInterval()

setInterval(function()
    postMessage('1');
, 1);

编辑:我错误地看到了不存在的递归,只是因为我正在寻找它。我仍然会使用setInterval 而不是setTimeout

【讨论】:

有什么区别? hi() 不是递归调用自己。它在回调中调用自己,这是完全不同的事情。 没有区别(除了可能不相关的性能差异)。 @torazaburo 好吧,这是我犯的一个令人尴尬的错误!我想主要的区别是可读性?!

以上是关于网络工作者突然终止的主要内容,如果未能解决你的问题,请参考以下文章

linux网络协议栈源码分析 - 传输层(TCP连接的终止)

linux网络协议栈源码分析 - 传输层(TCP连接的终止)

Docker 网络已禁用:警告:IPv4 转发已禁用。网络将无法正常工作

角 - 节省地创建和处置内联网络工作者

AWS EMR Presto 集群突然终止错误:作业流中的所有从属服务器都因 Spot 而终止

异常处理