workers-feature
原文发布在我的个人博客 初识 web Worker | 以太空间 https://blog.io-code.com/2018/05/10/初识Web-Worker/
一, 概述
众所周知, 不同于 Java/C# 这些编程语言, JavaScript 作为一门为浏览器而生的语言, 采用的是单线程模型, 也就是说, 所有任务排成一个队列, 一次只能做一件事. 随着电脑计算能力的增强, 尤其是多核 CPU 的出现, 这一点带来很大的不便, 无法充分发挥 JavaScript 的潜力. 不过 2009 年 Node.js 出现后, JavaScript 也可以依靠 Node.js 的 cluster 模块以多线程的方式运行, 但是这种方式必须要在 Node 环境中才行, 那么在浏览器中如何以多线程的方式运行 JavaScript 呢?
Web Worker 是 html5 标准的一部分, 这一规范定义了一套 API, 它允许一段 JavaScript 程序运行在主线程之外的另外一个线程中, 换句话说, 允许主线程将一些任务分配给子线程. 在主线程运行的同时, 子线程在后台运行, 两者互不干扰. 等到子线程完成计算任务, 再把结果返回给主线程. 因此, 每一个子线程就好像一个 "工人"(worker), 默默地完成自己的工作. 这样做的好处是, 一些高计算量或高延迟的工作, 被 worker 线程负担了, 所以父进程 (通常是 UI 进程) 就会很流畅, 不会被阻塞或拖慢.
Web Worker 有以下几种工作线程:
专用线程(Dedicated Worker): 只能为一个页面所使用, 只能与创造它们的父进程通信.
共享线程 (Shared Worker): 可以被多个页面(必须同域) 获取并使用.
Service Worker: 一个在网络应用与浏览器或网络层之间的代理层, 它可以拦截网络请求, 使得离线访问成为可能.
Web Worker 有以下几个特点:
同域限制: 子线程加载的脚本文件, 必须与主线程的脚本文件在同一个域.
DOM 限制: 子线程所在的全局对象, 与父进程不一样, 它无法读取网页的 DOM 对象, 即 document,window,parent 这些对象, 子线程都无法得到.(但是, navigator 对象和 location 对象可以获得.)
脚本限制: 子线程无法读取网页的全局变量和函数, 也不能执行 alert 和 confirm 方法, 不过可以执行 setInterval 和 setTimeout, 以及使用 XMLHttpRequest 对象发出 AJAX 请求.
文件限制: 子线程无法读取本地文件, 即子线程无法打开本机的文件系统(file://), 它所加载的脚本, 必须来自网络.
二, 基础 API
1. 新建和启动子线程
首先在父进程 (假设父进程文件是 main.js) 中调用构造函数(Worker), 传入子进程脚本的文件名.
- // File: main.js
- const worker = new Worker('worker.js');
这个子进程脚本必须来自网络端, 如果下载失败的话, 子进程也启动不了了.
子线程新建之后, 并没有启动, 必需等待主线程调用 postMessage 方法, 即发出信号之后才会启动. postMessage 方法的参数, 就是主线程传给子线程的信号. 它可以是一个字符串, 也可以是一个对象.
- // File: main.js
- worker.postMessage("Hello World");
- worker.postMessage({
- method: 'echo',
- args: ['Work']
- });
注意: 只要符合父线程的同源政策, Worker 线程自己也能新建 Worker 线程. Worker 线程可以使用 XMLHttpRequest 进行网络 I/O, 但是 XMLHttpRequest 对象的 responseXML 和 channel 属性总是返回 null.
2. 父子进程的事件监听
首先在父进程中对子进程进行消息监听
- // File: main.js
- worker.onmessage = function(e) {
- let { data } = e;
- console.log(data);
- };
- // 或者
- worker.addEventListenr(function (e) {
- console.log(e.data);
- });
- ```
然后在子进程中对父进程进行消息监听
- ```js
- // File: worker.js
- self.onmessage = function(event) {
- let method = event.data.method;
- let args = event.data.args;
- console.log(method, args);
- };
- // 或者
- self.addEventListener('message', function(e) {
- console.log(e.data);
- });
3. 父子进程的数据通信
父进程向子进程发送消息
- // File: main.js
- worker.postMessage('Hello, My honey bady');
子进程向父进程发送消息
- // File: worker.js
- self.postMessage('Hello, My daddy');
4. 错误处理
父线程可以监听子线程是否发生错误. 如果发生错误, 会触发主线程的 error 事件.
- // File: main.js
- worker.onerror(function(event) {
- console.log(event);
- });
- // 或者
- worker.addEventListener('error', function(event) {
- console.log(event);
- });
5. 关闭子线程
使用完毕之后, 为了节省系统资源, 我们必须在主线程调用 terminate 方法, 手动关闭子线程.
- // File: main.js
- worker.terminate();
也可以子线程内部关闭自身.
- // File: worker.js
- self.close();
三, 图解 worker 模型
webKit 加载并执行 worker 线程的流程如下图所示
worker-model
以下内容来自 AlloyTeam 团队
1. worker 线程的创建的是异步的
代码执行到
let worker = new Worker(task.js')
时, 在内核中构造 WebCore::JSWorker 对象 (JSBbindings 层) 以及对应的 WebCore::Worker 对象(WebCore 模块), 根据初始化的 url 地址 task.js 发起异步加载的流程; 主线程代码不会阻塞在这里等待 worker 线程去加载, 执行指定的脚本文件, 而是会立即向下继续执行后面代码.
2. postMessage 消息交互由内核调度
main.js 中, 在创建 woker 线程后, 立即调用了 postMessage 方法传递了数据, 在 worker 线程还没创建完成时, main.js 中发出的消息, 会先存储在一个临时消息队列中, 当异步创建 worker 线程完成, 临时消息队列中的消息数据复制到 woker 对应的 WorkerRunLoop 的消息队列中, worker 线程开始处理消息. 在经过一轮消息来回后, 继续通信时, 这个时候因为 worker 线程已经创建, 所以消息会直接添加到 WorkerRunLoop 的消息队列中.
四, worker 线程数据通讯方式
主线程与子线程数据通信方式有多种, 通信内容, 可以是文本, 也可以是对象. 需要注意的是, 这种通信是拷贝关系, 即是传值而不是地址, 子线程对通信内容的修改, 不会影响到主线程. 事实上, 浏览器内部的运行机制是, 先将通信内容串行化, 然后把串行化后的字符串发给子线程, 后者再将它还原.
参考链接
[1] AlloyTeam 深入理解 Web Worker http://www.alloyteam.com/2015/11/deep-in-web-worker/
[2] 阮一峰 Web Worker http://javascript.ruanyifeng.com/htmlapi/webworker.html#toc7
来源: http://www.jianshu.com/p/c48be4cad361