前言
为什么说多线程如此重要?这是个值得思考的问题。一直以来,派生线程以一种优雅的方式实现了对同一个进程中任务的划分。操作系统负责分配每个线程的时间片,具有高优先级并且任务繁重的线程将分配到更多的时间片,而低优先级空闲的线程只能分到较少的时间片。
虽然多线程如此重要,但JavaScript却并没有多线程的能力。幸运的是,随着 Web Worker 的普及,我们终于可以在后台线程来处理资源密集型的计算了。而不好的方面是,目前制定的标准只适用于当前的生态系统,这有时候就比较尴尬了。如果你了解其他从一开始就支持多线程的语言的话,你可能会发现很多的限制,远非仅仅是实例化一个新线程,然后你操控这个实例就能实现多线程。
这篇文章主要来介绍 Web Worker,包括什么时候使用,该怎么使用,它有什么奇怪的特性,会介绍在 Webpack 中如何使用它,还有可能遇到的一些坑。
一、Web Workers
Web Worker 可能是在 JavaScript 中唯一可以真正实现多线程的方法了。我们需要按照下面的方式创建 worker :
上面就定义了一个 Worker 实例,然后你可以通过 postMessage 与 worker 通信,就像和 iFrame 通信一样,只不过不存在跨域的问题,不需要验证跨域。
在 worker 代码中,你需要监听这些事件:
这种方式是双向的,所以你也可以从 worker 中 postMessage 给我们的主程序。
在 worker 代码中:
在主程序中:
这就是 worker 最基本的用法。
异常处理
在你的 worker 代码中,有很多种方式来处理异常,比如你可以 catch 之后通过 postMessage 传递,这样可能需要多些一些代码,但是确实最有效也最安全的。
另一种方式是用 onerror
事件,这种方式可以捕捉所有未处理的异常,并且交给调用方来决定如何处理。调用方式很简单:
为了调试方便,异常对象中还有一些额外的字段比如:filename
,lineno
,colno
.
回收
将不需要的 worker 回收是非常重要的,worker 会生成真正的操作系统线程,如果你发现与很多 worker 线程同时运行,你可以通过很简单的杀掉浏览器进程。
你有两种方式杀掉 worker 进程:在 worker 里和在 worker 外。我认为最好的处理 worker 生命周期的地方是在主页面里,但这也要取决于你代码的具体情况。
杀掉一个 worker 实例,在外部可以直接调用 terminate()
方法,这种方法可以立即杀掉它,释放所有它正在使用的资源,如果它正在运行,也会立即终止。
如果你想要让 worker 自己去管理它的生命周期,可以直接在 worker 代码中调用stop()
方法。
不管使用哪种方法,worker 都会停止,销毁所有资源。
如果你想使用一种“一次性”的 worker,比如需要做一些复杂运算之后就不再使用了,也要确保在 onerror
事件中去销毁它们,这样有利于规避一些难以发现的问题。
二、行内 Workers
有些时候将 worker 代码写到一个外部文件可能会使原本简单的问题变得复杂,幸运的是,workers 也可以用一个 Blob 来初始化。
写一个行内 worker ,参考如下代码段: