Web Workers是WHATWG中一个很酷的特性。
JavaScripts 本身是一种单线程设计,无法在同一时刻并行的运行多个脚本。浏览器处理的每一个任务都是通过串行的方式进行处理。当使用setTimeout()和setInterval()这样的函数时,可能会产生多个线程独立于JavaScript主线程同时运行的错觉,但事实上,这些函数其实都被加入了主线程使用得同一个事件循环里。这种方式有一个缺点,一旦使用任何会导致阻塞的函数,就会使浏览器失去响应。 如Ajax中使用的XMLHttpRequest 对象有同步和异步两种模式。异步模式使用的频率要远远高于同步模式,因为同步的请求会牵制整个线程,在请求返回前会阻塞所有后续指令的执行。这会导致所有与页面的交互行为全部失效,表现出来的效果就是页面虽然显示出来了,但是会像冻住一样没有反应。
Web Workers 通过引入类似线程的机制是这种问题得到有效的解决。使用Web Workers,创建一个工作线程就是能够简单地加载一段脚本并在后台线程中执行。 一个运行在工作线程中的脚本是无法影响或阻塞主线程的,这意味着可以一边进行cpu密集型的处理,同时用户继续运行游戏或者应用。
工作线程分为专用线程(Dedicated Worker)和共享线程(Shared Worker):
专用线程只适合当前客户端使用,与其他客户端无关联,适用于本客户端内(本浏览器)的多线程使用。共享线程适用于多个客户端(多个浏览器)之间进行数据交互和控制,但是html5中没有类似锁机制,所以安全性存在一定问题。
专用线程:
创建一个工作线程需要使用Worker( )构造函数,参数是需要在另一个线程中运行的JavaScript文件名称。
var worker = new Worker("myworker.js");
然后在实例上监听onmessage事件,来获取消息。
worker.onmessage = function(event){
//从工作线程获取消息
}
另一种方法:
worker.addEventListener("message",function(event){
//从工作线程获取消息
},false);
在两种方式下,你都可以在event对象的data属性中找到消息数据。数据已经自动地由JSON格式解码为原始格式,因此数据结构没有任何改变。
最后通过调用postMessage()函数在不同线程中传递数据。
工作线程和它们的父线程通过一组公共的消息API进行通信。数据都是通过字符串的形式进行传递的,但是这并不意味着你只能发送字符串形式的而消息。如果你发送一些复杂的结构体,比如对象或者数组,它们将自动转换成JSON格式。但是DOM元素是不能转换成JSON的。
当你使用工作线程完成了既定的工作时,需要调用terminate()方法来释放和避免僵尸线程的状态。
worker.terminate();
共享线程:
共享线程与专用线程:二者的区别在于共享线程可以有多个连接。用于解决多连接并发的问题。它们并不是绑定于一个HTML页面的。如果你在同一个浏览器上打开了同一个网站的页面,这些页面都可以访问其中任意页面创建的共享工作线程。
可以使用 SharedWorker()构造函数,创建共享工作线程。
除了脚本的路径外,这个构造函数还需要一个可选的name参数。如果name参数没有指定会使用一个空字符串。如果创建一个和既有实例使用相同脚本和名字的共享工作线程,只会为已存在的线程增加一个新的连接而并不会创建一个全新的线程。
一个检测是否为素数的例子:
这个有用的例子展示了如何使用一个工作线程来运行一个工作线程来进行cpu密集型的处理工作,以便把主线程解放出来。
创建了一个prime.js文件,一个简单的判断一个数字n是否为素数的穷举算法程序。
/** * Created by Huangpingyi on 2016/7/25. */ function isPrime(n) { if(n<2) return false; for(var i= 2,m=Math.sqrt(n);i<m;i++){ if(n%i === 0){ return false; } } return true; }
创建测试页面,因为对于一个素数不会中途退出,足够大的素数会让isPrime( )函数忙上一段时间,这样就能在一段时间内使UI线程有效地停止响应。
这个简单的测试页面包括一个文本页面和两个按钮。当点击check按钮时,文本框number的值传给isPrime( )函数,结果通过消息框提示。
第二个按钮是click-test用来检测UI是否响应的。当isPrime()正在运行时试着点击这个按钮,在isPrime( )函数结束并显示结果之前,是不会有任何反应的。
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>Prime Number</title> <script src="prime.js"></script> <script src="sizzle.js"></script> </head> <body> Number (n): <input id="number" value="1125899839733759"> <button id="check">IS n prime?</button><br/><br/> <button id="click-test">Try to click me!</button> <script> var $ = Sizzle; $("#check")[0].addEventListener("click",function(){ var n = parseInt($("#number")[0].value, 10), res = isPrime(n); if(res) { alert(n + " is a prime number"); } else{ alert(n + " is not a prime number"); } },false); $("#click-test")[0].addEventListener("click",function(){ alert("Hello!"); },false); </script> </body> </html>
如果把isPrime( )函数委托给一个工作线程,那么UI线程将始终保持空闲,用户与浏览器的交互始终都能得到即时响应。
使用工作线程的prime-worker.js
/** * Created by Huangpingyi on 2016/7/25. */ var c = 0; addEventListener("connect",function(event){ var id = c++, port = event.ports[0]; port.postMessage("You are now connected as #"+id); port.addEventListener("message",function(event){ if (event.data == "Hello") { port.postMessage("And hello to you ,#" +id); } },false); port.start(); },false) ;
与工作线程通信,当check按钮被单击的时候,prime-worker.js脚本创建一个新的工作线程。
number文本框的值转换成整型之后通过postMessage( )函数投递给工作线程。message事件处理函数等待工作线程的响应,然后把结果通过消息框展示出来。
如果你现在试着单击测试按钮,就会看到UI仍然可以迅速地显示出“Hello!”的响应。所有繁重的计算过程都是独立在后台进行处理,不会对页面造成影响。
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>Prime Number</title> <script src="prime.js"></script> <script src="sizzle.js"></script> </head> <body> Number (n): <input id="number" value="1125899839733759"> <button id="check">IS n prime?</button><br/><br/> <button id="click-test">Try to click me!</button> <script> var $ = Sizzle; <span style="color:#000099;">$("#check")[0].addEventListener("click",function(){ var n = parseInt($("#number")[0].value, 10), worker = new Worker("prime-worker.js"); worker.addEventListener("message",function(event){ if(event.data){ alert(n + " is a prime number"); } else{ alert(n + " is not a prime number"); } },false); worker.postMessage(n); },false);</span> $("#click-test")[0].addEventListener("click",function(){ alert("Hello!"); },false); </script> </body> </html>
共享工作线程页面
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <textarea cols=80 rows=20 id="output"></textarea> <script> var worker = new SharedWorker("shared-worker.js","worker"); worker.port.addEventListener("message",function(event){ document.getElementById("output").value += event.data+ "\r\n"; },false); worker.port.start(); worker.port.postMessage("Hello") </script> </body> </html>
每当这个页面从工作线程接收到一条消息的时候,这条消息被输出到负责输出的textarea中。当连接建立后,这个工作线程收到一条内容为“Hello”的问候消息。
shared-worker.js脚本
/** * Created by Huangpingyi on 2016/7/25. */ var c = 0; addEventListener("connect",function(event){ var id = c++, port = event.ports[0]; port.postMessage("You are now connected as #"+id); port.addEventListener("message",function(event){ if (event.data == "Hello") { port.postMessage("And hello to you ,#" +id); } },false); port.start(); },false) ;
共享线程不像专用工作线程那样拥有一个全局的message事件。他们必须来监听connect事件来获取新页面何时向共享工作线程创建创建连接。工作线程和创建连接那个线程之间的通信是基于触发message事件的port对象,利用的是其提供的postMessage( )。必须要调用port.start( )才能开始接收消息。
如上述例子,HTML文件加载完成时,它会连接一个工作线程。如果在另一个标签页中打开了同一个或者类似的页面,它会连接与之前相同的工作线程,你会看到计数器的值相应增加。