JS的多线程 - Web Worker

线程与进程

进程(Process)是系统资源分配和调度的单元。一个运行着的程序就对应了一个进程。一个进程包括了运行中的程序和程序所使用到的内存和系统资源。如果是单核CPU的话,在同一时间内,有且只有一个进程在运行。但是,单核CPU也能实现多任务同时运行,比如你边听网易云音乐的每日推荐歌曲,边在网易有道云笔记上写博文。这算开了两个进程(多进程),那运行的机制就是一会儿播放一下歌,一会儿响应一下你的打字,但由于CPU切换的速度很快,你根本感觉不到,以至于你认为这两个进程是在同时运行的。进程之间是资源隔离的。

那线程(Thread)是什么?线程是进程下的执行者,一个进程至少会开启一个线程(主线程),也可以开启多个线程。比如网易云音乐一边播放音频,一边显示歌词。多进程的运行其实也就是通过进程中的线程来执行的。一个进程下的线程是可以共享资源的,所以在多线程的情况下,需要特别注意对临界资源的访问控制.

浏览器

一个浏览器通常由以下几个常驻的线程:

  • 渲染引擎线程:负责页面的渲染
  • JS引擎线程:负责JS的解析和执行
  • 定时触发器线程:处理定时事件,比如setTimeout, setInterval
  • 事件触发线程:处理DOM事件
  • 异步http请求线程:处理http请求

需要注意的是,渲染线程和JS引擎线程是不能同时进行的。渲染线程在执行任务的时候,JS引擎线程会被挂起。因为JS可以操作DOM,若在渲染中JS处理了DOM,浏览器可能就懵逼了。

JavaScript是多线程还是单线程?

JavaScript语言的一大特点就是单线程。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

随着电脑计算能力的增强,尤其是多核 CPU 的出现,单线程带来很大的不便,无法充分发挥计算机的计算能力。HTML5提出 Web Worker 标准,允许JavaScript脚本创建多个线程,但是 子线程完全受主线程控制,且不得操作DOM。所以,这个新标准 并没有改变JavaScript单线程的本质

HTML5 - Web Worker

Web Worker 概念

JS提供了一个 Worker 的类,当我们使用这个类的时候,它就会向 浏览器 申请一个新的线程。这个线程就用来 单独执行一个js文件。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。

Web Worker 使用注意点:

  • 同源限制
    分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。

  • DOM 限制
    Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用documentwindowparent这些对象。但是,Worker 线程可以使用navigator对象和location对象。

  • 通信联系
    Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过 消息 完成。

  • 脚本限制
    Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。

  • 文件限制
    Worker 线程无法读取本地文件,即不能打开本机的文件系统 (file://)它所加载的脚本,必须来自网络

Web Worker 使用

  • 主线程新建worker线程
var worker = new Worker("work.js");
  • 主线程向worker线程 发消息
worker.postMessage(参数);      //参数就是主线程传给 Worker 的数据,可以是文本、对象等等
  • 主线程 接收 来自worker线程的消息
worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
}
  • 主线程 关闭 worker线程
worker.terminate();
  • worker线程 接收 来自主线程的消息
    Worker 线程内部需要有一个监听函数,监听 message 事件。
    其中,self代表子线程自身,即子线程的全局对象
this.addEventListener('message', function (e) {
  self.postMessage('You said: ' + e.data);
}, false)
  • worker线程向主线程 发送 消息
self.postMessage('this is worker');
  • worker线程 关闭 自身
self.close();
  • 错误处理
// 主线程
Worker.onerror:指定 error 事件的监听函数。

//worker线程
self.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。

Web Worker 数据通信

主线程给worker线程发送的数据是 文本、对象 的话是拷贝的形式发送。即是传值而不是传址,Worker 对通信内容的修改,不会影响到主线程。事实上,浏览器内部的运行机制是,先将通信内容串行化,然后把串行化后的字符串发给 Worker,后者再将它还原。

主线程给worker线程发送的数据是 二进制数据,但是,拷贝方式发送二进制数据,会造成性能问题。比如,主线程向 Worker 发送一个 500MB 文件,默认情况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript 允许主线程把二进制数据直接转移给子线程,但是一旦转移,主线程就无法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面。这种转移数据的方法,叫做Transferable Objects。这使得主线程可以快速把数据交给 Worker,对于影像处理、声音处理、3D 运算等就非常方便了,不会产生性能负担。

参考资料

posted @ 2020-05-25 02:34  _Sleeping  阅读(977)  评论(0)    收藏  举报