浏览器的js脚本运行一直都是单线程运行的,所以我们不需要考虑多线程同步加锁这种情况。但是当我们需要做一些比较耗时的计算时候如果还放在这个单线程里面,可以想象页面会卡主。

其实浏览器也是支持多线程运行的叫做web workers。通过web workers可以把耗时的计算放在非主线程里面。从而充分发挥电脑的性能。下图是浏览器对web worker的支持情况:

Web多线程之WebWorkers_nodejs

本章主要从三个方面进行研究:

1、web worker初探

2、主线程与worker线程通信

3、第三方库webworkify使用

web worker初探

创建一个webworker只需要两步:

1、一个js文件

2、new Worker

我们创建一个worker.js的文件,并计算一个求和操作,js内容如下:

let sum = 0
let num = 10000
for (let i = 0; i < num; i++) {
  sum = sum + i
}
console.log('worker compute sum = %d', sum)

在html中通过new Worker即可创建一个worker子线程

<script>
  let worker = new Worker('./worker.js')
</script>

通过上述两个步骤,即可实现一个最简单的worker子线程。并且在控制台打印出计算结果:

Web多线程之WebWorkers_js_02

释放worker子线程

worker子线程会耗费大量的资源,而且不会自动的被释放,通过上述代码创建完之后,如果不需要了,我们需要手动释放。释放worker子线程有两种方法。

方法一:在html中,通过创建出来的worker对象来释放:

worker.terminate()

方法二:在worker.js中调用self.close()来释放

self.close()

主线程与worker线程通信

worker线程是一个独立的空间,与主线程之间也是完全隔离的。我们worker线程计算出来的结果想要在dom上显示必须通过主线程来操作。所以需要解决两者之间的通信问题。这里浏览器提供了onmessage和postMessage来实现两者之间的通信。

主线程通过onmessage来接收worker线程消息,通过postMessage来往worker线程发送消息。

worker线程也类似,通过onmessage来接收主线程的消息,通过postMessage来往主线程发送消息。

主线程示例:

<script>
  let worker = new Worker('./worker.js')
  worker.onmessage = (event) => {
    console.log(event)
  }
  worker.postMessage(`i am from main thread`)
</script>

worker线程示例:

let sum = 0
let num = 10000
for (let i = 0; i < num; i++) {
  sum = sum + i
}
console.log('worker compute sum = %d', sum)
self.postMessage(`worker compute sum = ${sum}`)
self.onmessage = (event) => {
  console.log(event)
}

运行之后,打印出来如下,我们成功的在主线程和子线程接收到了双方发过来的消息。如下图:

Web多线程之WebWorkers_nodejs_03

这种消息传递方式在正常的计算结果或者规模不大的对象传递是没有问题。但是当我们用来传递数据很大的ArrayBuffer的时候,会产生大量的内存和资源消耗。因为我们调用postMessage的时候传递的对象会重新被克隆一份出来,在onmessage函数回调的其实是另一个对象。这就是产生资源消耗变多的原因。

这里浏览器为我们提供了可转移对象来帮我们共享同一份内存。

可转移对象

可转移对象是把一个对象的所有权从主线程转移到子线程或者过程相反,这样我们就不需要来回的拷贝对象了,只是在主线程和子线程之间切换了对象的所有权。这种情况适合于某个对象每次只在一个地方使用的场景。

这种模式也是通过postMessage来传递消息,只不过需要增加第二个参数。看下面的示例:

  const arrayBuffer = new ArrayBuffer(32)
  worker.postMessage(arrayBuffer, [arrayBuffer])

通过上述写法,我们实现了将主线程的ArrayBuffer对象转移到了子线程。

html的示例:

<script>
  let worker = new Worker('./worker.js')
  worker.onmessage = (event) => {
    console.log(event)
  }
  const arrayBuffer = new ArrayBuffer(32)
  worker.postMessage(arrayBuffer, [arrayBuffer])
  console.log(arrayBuffer)
</script>

worker.js的示例:

let sum = 0
let num = 10000
for (let i = 0; i < num; i++) {
  sum = sum + i
}
console.log('worker compute sum = %d', sum)
self.postMessage(`worker compute sum = ${sum}`)
self.onmessage = (event) => {
  console.log(event)
}
const arrayBuffer = new ArrayBuffer(64)
self.postMessage(arrayBuffer, [arrayBuffer])
console.log(arrayBuffer)

看下我们的输出:

Web多线程之WebWorkers_javascript_04

我们输出的arrayBuffer都是一个空对象,因为里面的内容被转移了。

webworkify

webworkify是一个npm包,通过这个包我们可以在worker.js中使用require()来引用其他module的函数。我们不可能通过一个worker.js就把业务都能搞定,而且现在前端都是模块化的开发,所以webworkify的作用还是很大的。

github地址:

https://github.com/browserify/webworkify

安装webworkify

npm install webworkify 

我们对前面的累计求和进行一下改造,新建一个add.js的文件并写一个add的函数:

class AddTest {
  add(a, b) {
    return a + b
  }
}
module.exports = AddTest

然后我们在worker.js中通过require引入这个对象,并把求和计算换成add函数。worker.js的写法如下,其他都没变,增加了module.exports和修改了求和计算。

var AddTest = require('./add');


module.exports = function (self) {
  let addTest = new AddTest()
  let sum = 0
  let num = 10000
  for (let i = 0; i < num; i++) {
    sum = addTest.add(sum, i)
  }
  console.log('worker compute sum = %d', sum)


  self.postMessage(`worker compute sum = ${sum}`)


  self.onmessage = (event) => {
    console.log(event)
  }


  const arrayBuffer = new ArrayBuffer(64)
  self.postMessage(arrayBuffer, [arrayBuffer])


  console.log(arrayBuffer)
}

在写一个main.js来创建子线程,在这里我们引入了webworkify来替代原先默认的Worker。

var work = require('webworkify');
var worker = work(require('./worker.js'));
worker.addEventListener('message', (event) => {
  console.log(event)
})
const arrayBuffer = new ArrayBuffer(32)
worker.postMessage(arrayBuffer, [arrayBuffer])
console.log(arrayBuffer)

最后通过browserify来打包,Browserify 可以让你使用类似于 node 的 require() 的方式来组织浏览器端的 Javascript 代码,通过预编译让前端 Javascript 可以直接使用 Node NPM 安装的一些库。

这里我们使用命令:

browserify main.js > bundle.js

最后在html中引入bundle.js文件即可。

<body>
  <script src="bundle.js"></script>
</body>

最后的运行结果和前面的是一样的:

Web多线程之WebWorkers_多线程_05

但是我们通过webworkify来组织子线程的代码可以在子线程中进行模块化编程,提高代码的可读性。

写在最后:

可以关注本人公众号:

迷途小书童爱读书

或者扫描如下二维码:

Web多线程之WebWorkers_java_06

回复webworkers

即可获得

1、原生webworker示例

2、基于webworkify的示例