OpenHarmony开发者论坛
标题:
Web Worker,JavaScript 多线程
[打印本页]
作者:
liubo-688
时间:
2024-12-17 14:49
标题:
Web Worker,JavaScript 多线程
[md]我们都知道,JavaScript是单线程的语言,这是因为最初 js 被设计用于浏览器中,主要用于操作dom元素,实现用户和浏览器的交互,如果 js 是多线程,那么可能会出现多个线程同时操作一个dom元素的情况,造成浏览器混乱,所以为了避免这种复杂性,js 被设计成相对简单的单线程的语言。
但是,随着技术的发展,在多核CPU的时代,单线程无法充分发挥计算机的计算能力,于是,在HTML5中,提出了 Web Worker 标准。Web Worker 可以为 JavaScript 脚本创建多个线程,让我们可以将一些复杂的计算任务交给worker线程运行,避免主线程阻塞,等到Worker线程计算完毕再把结果返回主线程。但是子线程完全受主线程控制,有多种限制条件,包括不能操作DOM等。所以,Web Worker并没有改变js单线程的本质。
## 线程的分类
Web Worker线程其实可以分为几类,例如
* Dedicated Worker:专用线程
* Shared Worker:共享线程
* Service Workers:服务工作线程
......等等
本文主要讲的是Dedicated Worker,即专用线程,专用线程只能被创建它的脚本使用,一个专用线程对应着一个主线程,一般情况下,Web Worker线程运行的代码就是为了当前的脚本(页面)服务,所以专用线程也是最常用的一种线程。
初步使用
### 创建 Worker 线程
Worker 线程的创建通过Worker 构造函数
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg)
Worker 构造函数的第一个参数是一个脚本文件,这个文件不能是本地文件,因为Worker无法读取本地文件,直接写本地地址会报错,所以这个脚本文件必须来自服务器。
```
第二个可选参数是一个options对象,可以配置name值来指定Worker的名称,可用于区分多个Worker。
```
```
```
```
```
```
```
可以理解为当前创建worker线程的代码就是主线程,上面Worker 构造函数的参数脚本文件就是worker线程。
### 主线程与Worker线程通信
主线程收发数据
主线程创建 Worker 线程后,可以通过 `<span lang="EN-US">worker.postMessage()</span>` 方法向Worker发送数据。该数据可以是各种数据类型,包括二进制数据等。
然后通过 `<span lang="EN-US">worker.onmessage</span>` 或者 `<span lang="EN-US">worker.addEventListener('message', function(){})</span>`的方式接收Worker线程发送过来的数据。
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image006.jpg)
Worker线程收发数据
同样,Worker线程可以通过 `<span lang="EN-US">self.postMessage()</span>`给主线程发送数据
通过 `<span lang="EN-US">self.onmessage</span>`或者 `<span lang="EN-US">self.addEventListener('message', function(){})</span>`的方式接收
值得一提的是在Worker线程中,self 代表线程自身(主线程中self代表window),也可以用this代替self,或者干脆省略不写也是可以的,所以下面三种写法其实是一样的
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image008.jpg)
数据通信例子
例如我们有段worker线程的代码如下
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image009.png)
同时主线程的代码如下
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image011.jpg)
最终打印结果为
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image012.png)
可以发现上面代码 Worker线程在接收主线程发过来的数据后将其对象数据上的某个值修改,然后发送数据给主线程,主线程在接收时打印原先发生的data数据,发现name值没变,说明主线程和worker线程的这种数据通信是拷贝关系,而不是简单的传值。所以Worker线程对通信数据的修改并不会影响主线程的数据。
作者:凯哥爱吃皮皮虾
链接:
https://juejin.cn/post/7085011669583134734
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
## worker线程的限制
· Worker 脚本文件的限制
* Worker 线程无法读取本地文件,脚本文件需来自服务器
* 同源策略:Worker 线程运行的脚本文件,必须与主线程的脚本文件同源
· Worker 线程全局对象限制
* Dom限制:如前面所说,为了避免多个线程同时操作dom带来的复杂性,Worker线程不能访问 `<span lang="EN-US">document</span>`、`<span lang="EN-US">window</span>`、`<span lang="EN-US">parent</span>`对象。但是可以访问 `<span lang="EN-US">navigator</span>`对象和 `<span lang="EN-US">location</span>`对象
* Worker线程也无法使用 `<span lang="EN-US">alert()</span>`方法和 `<span lang="EN-US">confirm()</span>`方法。但是Worker可以访问XMLHttpRequest 对象,也就是发AJAX请求,也可以获取setTimeout(), clearTimeout(), setInterval(), clearInterval()等定时操作方法
· 数据通信限制:如上面所说,Worker线程和主线程并不在同一个上下文环境,不能直接通信。
## 本地调试方案
脚本文件必须来自网络,那么我们在本地调试的时候怎么调试呢?
其实方案有很多,这里给出几个常用的比较简单的方案供大家参考。
### 利用Blob
我们可以通过Blob()方式,首先
我们可以把Worker线程代码写出字符串的形式,再通过 new Blob() 和 window.URL.createObjectURL() 的方式来将其转化为可以生效的 worker 脚本文件
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image014.jpg)
如果不想通过这种字符串的形式,也可以用一个 script 标签将Worker线程代码包裹起来,并将其type类型设置为js无法识别的自定义类型,那么它就会被认为是一个数据块元素。
示例代码:
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image016.jpg)
然后通过document的方式获取其代码
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image018.jpg)
### 使用http-server
通过安装http-server
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image019.png)
然后在相关代码的文件夹下运行命令
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image021.jpg)
这样,便可以创建worker
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image023.jpg)
## Worker的错误处理
在主线程中通过 `<span lang="EN-US">worker.onerror</span>` 或者 `<span lang="EN-US">worker.addEventListener('onerror', function(){})</span>`的方式来监听Worker是否发生错误。
例如,
Worker线程代码:
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image025.jpg)
主线程代码:
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image027.jpg)
上面代码执行后worker线程中由于 undefined 上面没有 foreach 方法,于是报错如下![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image028.png)
Worker线程中也可以监听错误,不过只能拿到错误的消息数据
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image029.png)
## 关闭Worker
Worker一旦创建成功就会始终运行,所以Worker 也比较耗费资源,当Worker 使用完毕时,我们可以手动停止Worker,通过以下代码
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image031.jpg)
也可以在Worker线程中关闭
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image032.png)
## Worker中加载其他脚本
Worker内部如果需要加载其他的脚本,可以通过 `<span lang="EN-US">importScripts()</span>`来加载
举个简单的例子
下面代码是被加载的文件
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image034.jpg)
Worker线程的代码如下
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image036.jpg)
在主线程运行创建出上面的Worker线程后,控制台可以如期打印出 3 (1+2=3),也就是Worker线程成功引入了 `<span lang="EN-US">otherScript.js </span>`文件的代码。
### 同时引入多个文件
importScripts() 也可以同时引入多个文件
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image037.png)
### importScripts的阻塞性
importScripts 是同步的执行代码的,并且有一定的阻塞性,我们看下面两个实验。
首先,有以下的test.js文件代码
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image039.jpg)
Worker 线程代码
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image041.jpg)
主线程创建上面Worker 的线程运行后可以在控制台发现
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image042.png)
时间大概是 8 毫秒。
而相对的,我们的我们直接把那段代码写到Worker线程中
Worker 线程代码
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image044.jpg)
打印结果为
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image045.png)
时间大概是 3 毫秒。
所以,其实importScripts并不是很实用。
在实际开发中,我们肯定是用模块化开发,不可能把Worker的所有代码都写在一个文件上,那么在 importScripts 不实用的情况下,我们可以使用打包工具将Worker的代码打包成一个文件,例如在webpack的项目中,我们可以使用 webworkify-webpack 插件。
## webworkify-webpack
笔者之前做过一个在线教育的直播平台,在对师生聊天历史记录的数据处理时,就用到了webworkify-webpack这个插件创建Worker线程。
### 安装webworkify-webpack
在webpack项目下安装
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image046.png)
### webworkify-webpack的使用
首先,Worker线程代码需要在包裹在函数中,并用module.exports导出,该函数的参数就是该Worker线程的self。如下示例
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image048.jpg)
而主线程中,创建对应Worker如下示例
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image050.jpg)
上面代码中,work的参数可以用 `<span lang="EN-US">require.resolve</span>`来返回Worker线程文件的绝对路径,require.resolve还可以检查拼接好之后的路径是否存在。
### webworkify-webpack原理
以下是webworkify-webpack源码的部分截取,可以发现其原理和上面的通过Blob()方式创建Worker线程一样都是通过 Blob 的方式
![](file:///C:/Users/zhang/AppData/Local/Temp/msohtmlclip1/01/clip_image052.jpg)
### webworkify-webpack实例
接下来,我们结合Vue写一个计时器示例:
首先,创建Worker线程文件
[/md]
欢迎光临 OpenHarmony开发者论坛 (https://forums.openharmony.cn/)
Powered by Discuz! X3.5