【问题标题】:Since JavaScript is single-threaded, how are web workers in HTML5 doing multi-threading?既然 JavaScript 是单线程的,那么 HTML5 中的 Web Worker 是如何进行多线程的呢?
【发布时间】:2012-03-14 18:41:14
【问题描述】:

我一直在阅读有关 HTML5 中的网络工作者的文章,但我知道 JavaScript 是单线程的。

我的问题是:

那么网络工作者是如何进行多线程工作的呢?或者如果它不是真正的多线程,他们如何模拟它?我这里好像不太清楚。

【问题讨论】:

  • Workers 确实在不同的线程中运行。
  • 工作线程根据定义是独立的线程
  • 我无意冒犯,我是来学习和帮助的。我意识到我并不是什么都知道。

标签: javascript html web-worker


【解决方案1】:

正如几位 cmets 已经指出的,Workers 确实是多线程的。

一些可能有助于澄清你的想法的要点:

  • JavaScript 是一种语言,它没有定义线程模型,不一定是单线程的
  • 历史上大多数浏览器都是单线程的(尽管这种情况正在迅速变化:IEChromeFirefox),并且大多数 JavaScript 实现都发生在浏览器中
  • Web Workers 不是 JavaScript 的一部分,它们是一种可以通过 JavaScript 访问的浏览器功能

【讨论】:

  • 这就是我缺少区别的地方,它与浏览器规范有关,而不是 JavaScript 语言。
  • @JamesDrinkard 我认为主要问题是DOM 不容易被多线程化,并且 JavaScript 通常用于 DOM 操作。但 DOM 不是 JavaScript。
  • @JamesDrinkard javascript 仍然是单线程,即使是网络工作者。 Web Worker 不会以任何以前没有的方式使 javascript 成为多线程,这样想:你实际上是在启动另一个单线程进程并与之通信,就像你在 node.js 中一样。以.js 为例。浏览器中的 javascript 是单线程的,worker 中的 javascript 是单线程的,但是它们可以通过一个瘦通道相互通信。
  • 这就是我的理解:浏览器使用单个线程(具有 javascript 代码)来呈现 DOM 并处理相关的事件循环(也可以包含 javascript 代码来执行非 dom 的事情,例如添加两个数字)但是我们可以使用worker,通过它浏览器只能执行非dom(javascript仍然)代码。然后,我们可以在两者之间同步数据(这里插入多线程概念)。所以,套用@James Drinkard,“浏览器在单线程javascript中处理DOM”;我们仍然可以拥有其他 不接触 DOM 的 javascript 线程
【解决方案2】:

有点晚了,但我刚刚问了自己同样的问题,我想出了以下答案:
浏览器中的 Javascript 总是单线程的,一个基本的结果是“并发”对变量的访问(多线程编程的主要问题)实际上不是并发的;这是真的除了 webworkers,它们实际上是在单独的线程中运行并且必须以某种明确的方式处理对变量的并发访问

我不是 JavaScript 忍者,但我也坚信浏览器中的 JavaScript 是作为单线程进程提供的,而没有过多关注它是否真实或这种信念背后的基本原理。
支持这一假设的一个简单事实是,当使用 JavaScript 编程时,您不必关心对共享变量的并发访问。每个开发人员甚至都没有考虑问题,编写代码就好像每次访问变量都是一致的。
换句话说,您不必担心所谓的Memory model

实际上没有必要查看 WebWorkers 来涉及 JavaScript 中的并行处理。考虑一个(异步)AJAX 请求。想想你会多么粗心地处理对变量的并发访问:

var counter = 0;

function asyncAddCounter() {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4) {
      counter++;
    }
  };
  xhttp.open("GET", "/a/remote/resource", true);
  xhttp.send();
}

asyncAddCounter();
counter++;

进程结束时counter 的值是多少?它是2。 “并发”读取和写入并不重要,它永远不会导致1。这意味着对counter 的访问始终是一致的。 如果两个线程真正同时访问该值,它们都可以从读取0 开始,最后都写入1

在浏览器中,远程资源的实际数据获取对开发人员是隐藏的,其内部工作超出了 JavaScript API 的范围(浏览器让您根据 JavaScript 指令控制的内容)。对开发者而言,网络请求的result由主线程处理。
简而言之,request 的实际执行是不可见的,但 callback 的调用(通过自定义 JavaScript 代码处理结果)由主线程执行。
可能,如果不是 webworkers,“多线程”这个词永远不会进入 Javascript 世界。

请求的执行和回调的异步调用实际上是通过事件循环实现的,而不是多线程。这适用于几个浏览器,显然适用于 Node.js。以下是一些参考资料,在某些情况下有点过时,但我猜现在仍然保留了主要思想。

这就是为什么说 JavaScript 是 Event-driven 而不是多线程的原因。
请注意,JavaScript 因此允许异步 习惯用法,但不允许并行 执行 JavaScript 代码(在 webworkers 之外)。 异步这个词只是表示两条指令的结果可能会以加扰的顺序处理。

对于 WebWorker,它们是 JavaScript API,可让开发人员控制多线程进程。
因此,它们提供了显式方法来处理对共享内存的并发访问(在不同线程中读取和写入值),除其他外,这通过以下方式完成:

  • 您通过结构化克隆将数据推送到 Web Worker(这意味着新线程读取数据):The structured clone algorithm - Web APIs | MDN。本质上没有“共享”变量,而是为新线程提供了对象的新副本。
  • 您通过转移值的所有权将数据推送给网络工作者:Transferable - Web APIs | MDN。这意味着只有一个线程可以随时读取其值。
  • 对于网络工作者返回的结果(他们如何“编写”),主线程会在提示时访问结果(例如使用指令thisWorker.onmessage = function(e) {console.log('Message ' + e.data + ' received from worker');})。我想它一定是通过通常的事件循环来实现的。
  • 主线程和网络工作者访问真正共享的内存SharedArrayBuffer,使用Atomic 函数可以线程安全地访问。我在这篇文章中发现了这一点:JavaScript: From Workers to Shared Memory
  • 注意:网络工作者无法访问真正共享的 DOM!

【讨论】:

    【解决方案3】:

    您生成一个 .js 文件作为“worker”,它在单独的线程中运行进程。您可以在它和“主”线程之间来回传递 JSON 数据。但是,Worker 无权访问某些东西,例如 DOM。

    所以,比如说,你想解决复杂的数学问题,你可以让用户在浏览器中输入东西,将这些变量传递给 worker,让它在后台进行计算,而在你让用户做其他事情,或显示进度条或其他东西,然后当工作人员完成后,它将答案传回,然后将其打印到页面上。您甚至可以异步处理多个问题,并在答案完成时乱序传回答案。很整洁!

    【讨论】:

    • JSON(Javascript Object Notation)是存储在字符串中的,所以应该说可以传输字符串。根据这个问题,postMessage 可以发送各种对象的克隆:stackoverflow.com/questions/13761968/… 根据 MDN 文章(没有提到只有字符串是一个东西),您可以传输某些对象(这比复制更快):@987654322 @基于此,应该可以使用MessagePorts将Workers一起链接成一个管道...
    【解决方案4】:

    浏览器使用您要执行的 javascript 启动线程。所以它是一个真正的线程,有了这个 web workers 的东西,你的 js 就不再是单线程的了。

    【讨论】:

      【解决方案5】:

      声称“JavaScript 是一种语言,它没有定义线程模型,它不一定是单线程”的答案是直接从媒体文章中复制粘贴的......它在没有解决疑问的情况下混淆了。

      它不一定是单线程的,就像所有其他语言一样。是的...但是

      Javascript 是一种用于单线程编程的语言,这就是它的美妙之处,并使其简单且易于实现。 它是围绕单个调用堆栈设计的。 也许在未来,随着新的实现,它会成为一种多线程编程的语言......但现在,Mehhhhhh。

      Node V8 仍然是单线程的,但它通过在 C++ 编写的 LIBUV 上创建工作线程来实现多线程功能。

      同样,即使 Javascript 不适合多线程,您也可以通过使用浏览器 API 实现有限的多线程。

      每次你在浏览器上打开一个TAB,它都会创建一个新线程,这个过程和web worker是一样的。

      它在内部工作,但无法访问任何窗口对象。

      是的,如果它让他们开心,人们可能会称它为多线程, 但在 2021 年,答案是 “JS 用于单线程编程,(或单线程语言),但通过使用 Web Workers 等浏览器 API 可以实现有限的多线程”

      【讨论】:

        【解决方案6】:

        实际上,我认为主要的困惑在于人们正在寻找聪明的方法来同时做事。如果你仔细想想,JavaScript 显然不是多线程的,但我们有办法并行处理,那么这是怎么回事?

        提出正确的问题将带来答案。谁负责线程?上面有一个答案,说JS只是一种语言,不是线程模型。完全正确!JavaScript 与它无关。责任落在 V8 身上。检查此链接以获取更多信息-> https://v8.dev/ 所以 V8 允许每个 JS 上下文有一个线程,这意味着,无论你多么努力,产生一个新线程都是不可能的。然而,人们产生了所谓的工人,我们感到困惑。为了回答这个问题,我问你以下问题。是否有可能启动 2 V8 并且他们都解释一些 JS 代码?正好解决了我们的问题。工作人员与消息进行通信是因为它们的上下文不同。它们是对我们的上下文一无所知的其他事物,因此它们需要一些以消息形式出现的信息。

        【讨论】:

          【解决方案7】:

          众所周知,JavaScript 是单线程的:所有代码都排队并按顺序执行。

          使用 Web Workers,我们可以同时运行 JavaScript 进程(或者至少,在这种语言允许的情况下尽可能接近并发)。这种方法的主要好处是在不干扰用户界面的情况下处理后台线程中的数据操作。

          使用网络工作者:

          Web Workers 允许您在网页上并行运行 JavaScript,而不会阻塞用户界面。

          • Web 工作者在单独的线程中执行
          • 需要将所有工作代码托管在单独的文件中
          • 它们不会自动进行垃圾回收,因此您需要控制它们。
          • 运行worker useworker.postMessage(“”);
          • 要停止工作程序,调用者代码有两个方法sterminate() andclose() 来自 Worker 本身
          • 实例化 worker 会消耗一些内存。

          Web Worker 在一个独立的线程中运行。因此,他们执行的代码需要包含在单独的文件中。但在我们这样做之前,首先要做的是在您的主页中创建一个 newWorkerobject。构造函数采用工作脚本的名称:

          var worker = new Worker('task.js'); 
          

          如果指定的文件存在,浏览器会产生一个新的工作线程,它是异步下载的。在文件完全下载并执行之前,worker 不会开始。如果您的工作人员的路径返回 404,工作人员将静默失败。

          创建worker后,调用postMessage()方法启动它:

          worker.postMessage(); // Start the worker. 
          

          通过消息传递与 Worker 通信

          作品与其父页面之间的通信是使用事件模型和 postMessage() 方法完成的。根据您的浏览器/版本,postMessage() 可以接受字符串或 JSON 对象作为其单个参数。现代浏览器的最新版本支持传递 JSON 对象。

          下面是使用字符串将“Hello World”传递给 doWork.js 中的工作人员的示例。 worker 只是返回传递给它的消息。

          主脚本:

          var worker = new Worker('doWork.js'); 
          worker.addEventListener('message', function(e) { 
            console.log('Worker said: ', e.data); 
          }, false); 
          
           
          
          worker.postMessage('Hello World'); // Send data to our worker.
          

          doWork.js(工人):

          self.addEventListener('message', function(e) { 
          
            self.postMessage(e.data); // Send data back to main script 
          
          }, false); 
          

          当从主页调用postMessage() 时,我们的工作人员通过为messageevent 定义anonmessagehandler 来处理该消息。消息负载(在本例中为“Hello World”)可在Event.data 中访问。这个例子展示了 postMessage() 也是你将数据传回主线程的方法。方便!

          参考资料:

          http://www.tothenew.com/blog/multi-threading-in-javascript-using-web-workers/ 

          https://www.html5rocks.com/en/tutorials/workers/basics/

          https://dzone.com/articles/easily-parallelize-jobs-using-0

          【讨论】:

            猜你喜欢
            • 2012-05-16
            • 2019-03-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-03-20
            • 1970-01-01
            • 2014-03-10
            • 1970-01-01
            相关资源
            最近更新 更多