前言:一直都觉得只有java才能谈多线程这么高逼格的话题,可惜java和javascript的关系就好比雷锋跟雷峰塔的关系,不过在HTML5新特性中有一个web worker,关于它的理论如果没有相关demo去实践它可能理解起来会有点僵硬。本文是通过canvas里面的一个操作像素的demo来更为直观地展现webworker的性能。

js的单线程特性

JavaScript引擎是单线程运行的,JavaScript中耗时的I/O操作都被处理为异步操作,它们包括键盘、鼠标I/O输入输出事件、窗口大小的resize事件、定时器(setTimeout、setInterval)事件、Ajax请求网络I/O回调等。当这些异步任务发生的时候,它们将会被放入浏览器的事件任务队列中去,等到JavaScript运行时执行线程空闲时候才会按照队列先进先出的原则被一一执行,但终究还是单线程。只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如运行一个运算量非常大的函数),导致整个页面卡在这个地方,其他任务无法执行。

异步和单线程

正是因为单线程所以注定了js中的某些操作必须是异步的,举个粒子:

1
2
3
4
5
6
7
window.onload = function () {
console.log(111);
setTimeout(function() {
console.log(222);
}, 0);
console.log(333);
}

上面的代码依次输出111,333,222,因为运行js碰到属于异步操作的代码就会单独拎出来放在一个队列(setTimeout就属于异步操作,不管是不是0秒后执行),等待同步代码(不属于异步操作的代码就是同步了)执行完就依次执行队列里的操作。其实在head里面加载js的话window.onload本身也属于异步代码。

实现异步操作的方法

1、利用setTimout或回调实现异步

2、动态创建script标签

3、利用script提供的defer/async

4、es6里的promise

这里只是总结一下,觉得前三种都很鸡肋,用好回调和promise大法就好了。

什么是webworker

Web Worker 是HTML5标准的一部分,这一规范定义了一套 API,它允许一段JavaScript程序运行在主线程之外的另外一个线程中。工作线程允许开发人员编写能够长时间运行而不被用户所中断的后台程序, 去执行事务或者逻辑,并同时保证页面对用户的及时响应,可以将一些大量计算的代码交给web worker运行而不冻结用户界面。这是这个标题几乎标准的答案,好像还是可以理解的。

webworker的基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//---------------放在页面上的main.js:主线程
var worker = new Worker("worker.js");
// 主线程向子线程发送数据
worker.postMessage(data);
worker.onmessage = function(ev){
var data = ev.data;
// 主线程收到子线程的处理后的数据
};
//-----------------------------------//

//-------------放在后台的worker.js:子线程
self.onmessage = function(ev){
var data = ev.data //收到主线程发过来的源数据
var newdata = handle(data);
self.postMessage(newdata );//把处理好的数据发回去
};
//把一些运算不叫复杂或工作量大的js代码拎过来
function handle(data){ some coding... }
//----------------------------------//
//-----------------------------停止worker
// 方式一 main.js 在主线程停止方式
var worker = new Worker('./worker.js');
...
worker.terminate();

// 方式二、worker.js
self.close();

这里就先罗列一个最基本的流程语法吧,非常简单,需要特别注意:子线程里面的js代码所支持的语法非常有限,只支持ECMAscript的基本语法,具体范围是多大呢,我们都知道javascript大致分为ECMAscript、DOM、BOM、nodeJs。第一类是基础,DOM类是基于ECMAscript实现的去操作DOM树的,BOM类就是浏览器行为语法,而nodeJs则是基于ECMAscript去操作os、file、database、net等等之类的(不知道这样理解会不会被人打)所以它只支持像string、array、object…这样子的东西,连console.log和alert都不支持,相信大家肯定知道这个范围。还有上面说了线程是后台开的,这些文件自然要放在服务器环境下运行的。在子线程中还可以通过 importScripts( ‘another1.js’ ,’another2.js’ )来引入其他的js文件,是不是看到一点原生js模块化的影子呢,然而这其实没什么软用。

demo:用canvas语法操作十万颗像素点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//------------------------------------------------主线程
window.onload = function () {
var oc = document.getElementById("print");
var ogc = oc.getContext('2d');
ogc.fillStyle = "#fc6423";
var ali = document.getElementsByTagName('li');
for (var i = 0; i < ali.length; i++) {
ali[i].onclick = function () {
console.time(1);//------------------计时开始
ogc.clearRect(0, 0, oc.width, oc.height);
ogc.save();
var h = 200;
var str = this.innerHTML;
ogc.font = h * 0.85 + 'px impact';
ogc.textBaseline = 'top';
var w = ogc.measureText(str).width;
ogc.fillText(str, (oc.width - w) / 2, (oc.height - h) / 2);
var allarea = ogc.getImageData((oc.width - w) / 2, (oc.height - h) / 2, w, h);
console.log(w);
ogc.clearRect(0, 0, oc.width, oc.height);
var newarea = ogc.createImageData(w, h);
// var allarr = suiji(w * h, w * h / 10);
//这里开一个worker,为的就是把上一行代码:10组随机数的产生拎到子线程中去运行
var worker = new Worker('cutarr.js');
worker.postMessage(w*h);// 将数据发给子线程
worker.onmessage = function (ev) {
var allarr = ev.data;//收到处理后的数据
var inow = 0;
var timer = null;
timer = setInterval(function () {
for (var i = 0; i < allarr[inow].length; i++) {
newarea.data[allarr[inow][i] * 4] = allarea.data[allarr[inow][i] * 4];
newarea.data[allarr[inow][i] * 4 + 1] = allarea.data[allarr[inow][i] * 4 + 1];
newarea.data[allarr[inow][i] * 4 + 2] = allarea.data[allarr[inow][i] * 4 + 2];
newarea.data[allarr[inow][i] * 4 + 3] = allarea.data[allarr[inow][i] * 4 + 3];
}
ogc.putImageData(newarea, (oc.width - w) / 2, (oc.height - h) / 2);
if (inow == 9) {
inow = 0;
clearInterval(timer);
}
inow++;
}, 150)
ogc.restore();
}
console.timeEnd(1); //------------------计时结束
}
}
}
//--------------------------------------------------子线程
function suiji(all, part) {
var arr1 = [];
var allarr = [];
for (var i = 0; i < all; i++) {
arr1.push(i);
}
for (var i = 0; i < all / part; i++) {
var newarr = [];
for (var j = 0; j < part; j++) {
newarr.push(arr1.splice(Math.floor(Math.random() * arr1.length), 1));
}
allarr.push(newarr);
}
return allarr;
}
self.onmessage = function (ev) {
var arr = suiji(ev.data,ev.data/10);
self.postMessage(arr);
}

上面这段js代码要完成的是将一块区域内的近10万颗像素(可调文字大小改变)每隔150ms随机显示其1/10的像素点,最终形成一个完整的汉字,其中最耗时的过程在于随机数的产生,有几个像素点就进行几次random操作。这是由随机函数suiji来完成,这里单纯地把它拎到子线程中去处理。上面的代码是用了webworker的,不使用webworker:即把suiji()放在window.onload里面,直接用 var allarr = suiji(w h, w h / 10)来产生就好了。接下来对比一下两种请况下从点击每个字到打印计时一共花费多少时间:

不使用worker

不使用worker

使用worker

使用worker

一个接近2秒,一个10ms以内,这。。。而且前面提到:工作线程允许开发人员编写能够长时间运行而不被用户所中断的后台程序, 去执行事务或者逻辑,并同时保证页面对用户的及时响应,也就是说,当浏览器碰到这句代码:

1
var allarr = suiji(w * h, w * h / 10);

页面就会卡死近2000ms,不能有任何操作,比如说点击、右键,更不用说执行后面的代码了:

1
2
3
console.log(111);       
var allarr = suiji( w*h,w*h/10 );
console.log(222);

这样我看到:几乎在点击的同时就打印了111,而222、计时和分割线是在近2000ms之后同时打印的,因为上面三行代码都是同步的,而用了webworker则不存在卡死的请况,速度也快多了。如果面积小看不出有多少差别,可以如果把大小调到500,前者就达到了恐怖的15秒之多了。可是这时候用了webworker虽然不会造成卡死状态,但好像也快不了多少,也许在一个可控的范围内才能发挥它的性能吧,或者说本来js代码对多线程的支持就不好。大家可以自己去编写更复杂变态的函数去测验。

案例源码地址

部分典型的应用场景

Web Worker带来后台计算能力,Web Worker自身是由webkit多线程实现,但它并没有为Javasctipt语言带来多线程编程特性,我们现在仍然不能在Javascript代码中创建并管理一个线程,或者主动控制线程间的同步与锁等特性。Web Worker 只是浏览器(宿主环境)提供的一个能力/API。而且它不支持IE。

1、使用专用线程进行数学运算
Web Worker最简单的应用就是用来做后台计算,而这种计算并不会中断前台用户的操作

2、 图像处理
通过使用从canvas或者video元素中获取的数据,可以把图像分割成几个不同的区域并且把它们推送给并行的不同Workers来做计算

3、大量数据的检索
当需要在调用 ajax后处理大量的数据,如果处理这些数据所需的时间长短非常重要,可以在Web Worker中来做这些,避免冻结UI线程。

4、背景数据分析
由于在使用Web Worker的时候,我们有更多潜在的CPU可用时间,我们现在可以考虑一下JavaScript中的新应用场景。我们现在可以考虑一下JavaScript中的新应用场景。例如,我们可以想像在不影响UI体验的情况下实时处理用户输入。利用这样一种可能,我们可以想像一个像Word(Office Web Apps 套装)一样的应用:当用户打字时后台在词典中进行查找,帮助用户自动纠错等等。

参考文章:http://www.alloyteam.com/2015/11/deep-in-web-worker/#prettyPhoto