传统的Web应用程序都是单线程的,完成一项任务后才执行下一项的任务,因此应用程序效率自然不会高,甚至会出现网页没有响应的情况。HTML5新增了Web Workers 对象,使用Web Workers 对象可以在后台运行 JavaScript 程序,也就是支持多线程,从而提高了新一代 Web 应用程序的效率。
1、概述
下面介绍HTML5 Web Workers 的基本概念以及支持 Web Workers 对象的浏览器情况。
1)什么是线程
在学习编程时,通常都是从编写顺序程序开始的。例如,输出字符串、对一组元素进行排序、完成一些数学计算等。每个顺序程序都有一个开始,然后执行一系列顺序的指令,直至结束。在运行时的任意时刻,程序中只有一个点被执行。
线程是操作系统可以调度的最小执行单位,通常是将程序拆分成两个或多个并发运行的任务。一个线程就是一段顺序程序。但是线程不能独立运行,只能在程序中运行。
不同的操作系统实现进程和线程的方法也不同,但大多数是在进程中包含线程,Windows就是这样。一个进程中可以存在多个线程,线程可以共享进程的资源(如内存)。不同的进程之间则不能共享资源。
比较经典的情况是进程中的多个线程执行相同的代码,并共享进程中的变量。举个形象的例子,就好像几个厨师在同时做菜(每个厨师就好比是一个线程),他们共同使用一些食材(食材就好比是系统资源),每个厨师对食材的使用情况都会影响其他厨师的工作。
在单处理器的计算机中,系统会将CPU时间拆分给多线程。处理器在不同的线程之间切换。而在多处理器或多核系统中,线程则是真正地同时进行,每个处理器或内核运行一个线程。
线程和进程的对比如下。
- 进程通常可以独立运行,线程则是进程的子集,只能在进程运行的基础上运行。
- 进程拥有独立的私有内存空间,一个进程不能访问其他进程的内存空间;而一个进程中的线程则可以共享内存空间。
- 进程之间只能通过系统提供的进程间通信机制进行通信;而线程间的通信则简单得多。
- 一个进程中的线程之间切换上下文比不同进程之间切换上下文要高效得多。
在操作系统内核中,线程可以被标记成如下状态。
初始化(Init):在创建线程时,操作系统在内部会将其标识为初始化状态。此状态只在系统内核中使用。
就绪(Ready):线程已经准备好被执行。
延迟就绪(Deferred Ready):表示线程已经被选择在指定的处理器上运行,但还没有被调度。
备用(Standby):表示线程已经被选择在指定的处理器上运行。当该处理器上运行的线程因等待资源等原因被挂起时,调度器将备用线程切换到处理器上运行。只有一个线程可以是备用状态。
运行(Running):表示调度器将线程切换到处理器上运行,它可以运行一个线程周期(Quantum),然后将处理器让给其他线程。
等待(Waiting):线程可以因为等待一个同步执行的对象或等待资源等原因切换到等待状态。
过渡(Transition):表示线程已经准备好被执行,但它的内核堆已经被从内存中移除。一旦其内核堆被加载到内存中,线程就会变成运行状态。
终止(Terminated):当线程被执行完成后,其状态会变成终止。系统会释放线程中的数据结构和资源。
Windows线程的状态切换如下图所示。
![](https://i-blog.csdnimg.cn/blog_migrate/1a4726920bd1c195baa681415cdaf8e2.png)
2)什么是HTML5 Web Workers
Web Workers 是 HTML5 的一个亮点,使用它可以在后台独立运行不需要与用户进行交互的JavaScript程序。这就使得一些需要长时间运行的脚本与需要和用户交流的脚本之间可以互不干扰地运行。
后台运行的脚本可以称为Workers。通常Workers的工作量都是相对“重量级”的,启动一个Web Workers对象耗费的性能成本和维护一个Web Workers实例需要的内存成本都比较高,因此不建议大量使用Web Workers对象,只用于长期运行的后台运算,不要频繁地创建和销毁Web Workers对象。
有两种Web Workers:专用线程(Dedicated Worker)和共享线程(Shared Worker)。专用线程一旦创建,就只能与创建它的页面连接和通信,而共享线程则没有这个限制。
3)浏览器对Web Workers的支持情况
在JavaScript中可以使用typeof(Worker)检测浏览器对Web Workers的支持情况。如果typeof(Workers)等于undefined,则表明当前浏览器不支持Web Workers;否则表明支持。
在网页中定义一个按钮,单击此按钮时,会检测浏览器是否支持Web Workers。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Test</title>
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<button id="check" onclick="check();">检测浏览器是否支持Web Workers</button>
<script>
function check() {
if (typeof(Worker) != "undefined") {
alert("您的浏览器支持Web Workers。");
} else {
alert("您的浏览器不支持Web Workers。");
}
}
</script>
</body>
</html>
2、Web Workers 编程
下面介绍在JavaScript中进行Web Workers编程的具体方法。
1)创建Web Workers对象
要进行Web Workers编程,首先要创建一个Web Workers对象。可以使用new关键字创建一个Web Workers对象,语法如下。
var <Web Workers对象> = new Worker("<.js文件>");
<.js文件>为Web Workers对象在后台运行的JavaScript脚本。
Web Workers编程的实例。本实例创建一个Web Workers对象,在后台运行demo_workers.js脚本,每隔1秒钟就更新一次计数,并在页面中显示出来。使用<output>标签result显示计数。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Test</title>
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<output id="result"></output>
<button onclick="startWorker();">开始计数</button>
<script>
var w; // Web Workers对象
function startWorker() {
if (typeof(Worker) != "undefined") {
if (typeof(w) == "undefined") {
w = new Worker("./js/demo_workers.js"); // 创建Web Workers对象
}
// 在demo_workers.js中会定时发送消息,这里处理接收到的消息
w.onmessage = function (event) {
document.getElementById("result").innerHTML = event.data;
};
} else {
document.getElementById("result").innerHTML = "Sorry, your browser does not support Web Workers...";
}
}
</script>
</body>
</html>
程序中创建了一个Web Workers对象w,该对象在后台运行demo_workers.js脚本。在demo_workers.js中会每隔1秒钟定时发送消息,这里处理接收到的消息(onmessage事件),并将接收到的数据(计数值)显示在<output>标签result中。
var i = 0;
function timedCount() {
i = i + 1;
postMessage(i);
setTimeout("timedCount()", 1000);
}
timedCount();
setTimeout()方法用于在指定的毫秒数后调用函数或计算表达式,语法如下。
setTimeout(code, millisec)
参数code表示调用的函数后要执行的JavaScript代码串;参数millisec指定在执行代码前需等待的毫秒数。
提示:必须通过Web服务器访问网页,Web Workers对象才能正常工作,通过双击访问网页则不行。
Web Workers编程的另一个实例。本实例创建一个Web Workers对象,在后台运行worker.js脚本,统计所有质数,并在页面中显示出来。这里使用<output>标签result显示计数。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Test</title>
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<p>统计所有质数:<output id="result"></output></p>
<script>
var worker = new Worker("./js/worker.js");
worker.onmessage = function (event) {
document.getElementById("result").textContent = event.data;
};
</script>
</body>
</html>
worker.js脚本
var n = 1;
search: while (true) {
n += 1;
for (var i = 2; i <= Math.sqrt(n); i+=1) {
if (n % i == 0) {
continue search;
}
// 找到质数
postMessage(n);
}
}
2)终止Web Workers对象
调用terminate()方法可以终止Web Workers对象,语法如下。
worker.terminate();
在上述代码的网页中添加一个终止按钮,其定义代码如下。
<button onclick="stopWorker()">停止计数</button>
单击“停止计数”按钮,会运行stopWorker()函数,代码如下。
function stopWorker() {
worker.terminate();
}
程序调用terminate()方法终止worker对象,因此会停止计数。
3)共享线程
共享线程(Shared Worker)可以与多个页面保持连接和通信。
创建共享线程的方法如下。
var <Web Workers对象> = new SharedWorker("<.js文件>");
<.js文件>为共享线程在后台运行的JavaScript脚本。
SharedWorker对象可以通过端口(port)与js文件通信,方法如下。
worker.port.onmessage = function (e) {
// 消息处理
......
}
e.data中包含通信数据。
在.js文件中,需要定义连接处理函数,并可以在连接处理函数中使用端口(port)与页面通信,方法如下。
onconnect = function (e) {
var port = e.ports[0];
port.postMessage(......);
}
共享线程编程的实例。本实例在页面outer.html中使用框架模拟两个页面,在两个页面中分别创建一个SharedWorker对象在后台运行sharedworkers.js脚本,并实现与不同页面的通信。
outer.html的代码如下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Test</title>
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<pre id="log">Log:</pre>
<script>
var worker = new SharedWorker("./js/sharedworkers.js");
var log = document.getElementById("log");
worker.port.addEventListener('message', function (e) {
log.textContent += '\n' + e.data;
}, false);
worker.port.start();
worker.port.postMessage("在吗?");
</script>
<iframe src="inner.html"></iframe>
</body>
</html>
注意,如果使用worker.port.addEventListener()方法注册SharedWorker对象的消息处理函数,则需要使用worker.port.start()方法开启端口。
inner.html的代码如下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Test</title>
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<pre id="log">Inner Log:</pre>
<script>
var worker = new SharedWorker("./js/sharedworkers.js");
var log = document.getElementById("log");
worker.port.onmessage = function (e) {
log.textContent += '\n' + e.data;
}
</script>
</body>
</html>
sharedworkers.js的代码如下。
var count = 0;
onconnect = function (e) {
count += 1;
var port = e.ports[0];
// port.postMessage("你好!你的连接号为#" + count);
port.onmessage = function (e) {
port.postMessage("我在呢。");
}
}