有点晚了,但我只是问了自己同样的问题,并想出了以下答案:
浏览器中的Javascript始终是单线程的,其基本结果是“并发”访问变量(多线程编程的主要麻烦)实际上不是并发的; Webworkers例外,这是事实,它们实际上是在单独的线程中运行的,必须以某种显式的方式处理对变量的并发访问。
我不是JavaScript忍者,但我也深信浏览器中的JavaScript是作为一个单线程进程提供的,而没有过多关注它的真实性或这种信念背后的原理。
支持此假设的一个简单事实是,使用JavaScript进行编程时,您不必关心对共享变量的并发访问。 每个开发人员甚至都不会思考问题,而是在编写代码时就好像对变量的每次访问都是一致的。
换句话说,您不必担心所谓的内存模型。
实际上,无需查看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++;
流程结束时SharedArrayBuffer的值是多少? 是Atomic。并发读取和写入无关紧要,它永远不会导致1。这意味着对counter的访问始终是一致的。如果两个线程真正地同时访问该值,则它们都可以通过读取0开始,并且都在最后写入1。
在浏览器中,远程资源的实际数据获取对开发人员是隐藏的,并且其内部工作不在JavaScript API的范围之内(浏览器让您根据JavaScript指令进行控制)。 就开发人员而言,网络请求的结果由主线程处理。
简而言之,实际执行请求是不可见的,但是回调的调用(通过自定义JavaScript代码处理结果)是由主线程执行的。
可能的是,如果不是针对Web工作者的,那么“多线程”一词将永远不会进入Javascript世界。
请求的执行和回调的异步调用实际上是通过使用事件循环而不是多线程来实现的。 对于某些浏览器,尤其对于Node.js,都是如此。 以下是一些参考资料,在某些情况下有些过时了,但我想现在仍然保留主要思想。
Firefox:并发模型和事件循环-JavaScript | MDN
Chrome在某些操作系统中使用libevent。
IE:了解事件模型(Internet Explorer)
这就是为什么JavaScript被称为事件驱动而不是多线程的原因。
请注意,JavaScript因此允许异步惯用语,但不允许并行执行JavaScript代码(外部Webworker)。 术语“异步”仅表示以下事实:两条指令的结果可能以加扰的顺序处理。
对于WebWorkers,它们是JavaScript API,使开发人员可以控制多线程进程。
这样,它们提供了显式方式来处理对共享内存的并发访问(不同线程中的读取和写入值),并且除其他外,这可以通过以下方式来完成:
您通过结构化克隆将数据推送到Web Worker(这意味着新线程将读取数据):结构化克隆算法-Web API | MDN。 本质上没有“共享”变量,而是为新线程提供了对象的新副本。
您可以通过转让值的所有权将数据推送给Web Worker:可转让-Web API | MDN。 这意味着只有一个线程可以随时读取其值。
至于Web工作人员返回的结果(“写”的方式),主线程会在系统提示时访问结果(例如,使用指令SharedArrayBuffer)。 我想,它必须借助于通常的事件循环。
主线程和Web工作人员访问真正的共享内存SharedArrayBuffer,可使用Atomic函数安全地对其进行线程访问。 我在这篇文章中清楚地看到了这一点:JavaScript:从工作者到共享内存
注意:网络工作者无法访问真正共享的DOM!