js多线程编程

HTML5之Javascript多线程

Javascript执行机制

在HTML5之前,浏览器中JavaScript的运行都是以单线程的方式工作的,虽然有多种方式实现了对多线程的模拟(例如:Javascript 中的 setinterval 方法,setTimeout 方法等),但是在本质上程序的运行仍然是由 JavaScript 引擎以单线程调度的方式进行的。在 HTML5 中引入的工作线程使得浏览器端的 Javascript 引擎可以并发地执行 Javascript 代码,从而实现了对浏览器端多线程编程的良好支持。

Javascript中的多线程 - WebWorker

HTML5 中的 Web Worker 可以分为两种不同线程类型,一个是专用线程 Dedicated Worker,一个是共享线程 Shared Worker。两种类型的线程各有不同的用途。

专用型web worker

  专用型worker与创建它的脚本连接在一起,它可以与其他的worker或是浏览器组件通信,但是他不能与DOM通信。专用的含义,就是这个线程一次只处理一个需求。专用线程在除了IE外的各种主流浏览器中都实现了,可以放心使用。

创建线程

创建worker很简单,只要把需要在线程中执行的JavaScript文件的文件名传给构造函数就可以了。

线程通信

在主线程与子线程间进行通信,使用的是线程对象的postMessage和onmessage方法。不管是谁向谁发数据,发送发使用的都是postMessage方法,接收方都是使用onmessage方法接收数据。postMessage只有一个参数,那就是传递的数据,onmessage也只有一个参数,假设为event,则通过event.data获取收到的数据。

发送JSON数据

JSON是JS原生支持的东西,不用白不用,复杂的数据就用JSON传送吧。例如:

postMessage({'cmd': 'init', 'timestamp': Date.now()});

处理错误

当线程发生错误的时候,它的onerror事件回调会被调用。所以处理错误的方式很简单,就是挂接线程实例的onerror事件。这个回调函数有一个参数error,这个参数有3个字段:message - 错误消息;filename - 发生错误的脚本文件;lineno - 发生错误的行。

销毁线程

在线程内部,使用close方法线程自己销毁自己。在线程外部的主线程中,使用线程实例的terminate方法销毁线程。

HTML代码:

<script type="text/javascript">

onload = function(){

var worker = new Worker('fibonacci.js');

worker.onmessage = function(event) {

console.log("Result:" + event.data);

};

worker.onerror = function(error) {

console.log("Error:" + error.message);

};

worker.postMessage(40);

}

</script>

脚本文件fibonacci.js代码:

//fibonacci.js

var fibonacci = function(n) {

return n < 2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2);

};

onmessage = function(event) {

var n = parseInt(event.data, 10);

postMessage(fibonacci(n));

};

把它们放到相同的目录,运行页面文件,查看控制台,可以看到运行的结果。

这里还有一点,在主线程中,onmessage事件可以使用另外一种方式挂接:

worker.addEventListener('message', function(event) {

console.log("Result:" + event.data);

}, false);

个人觉得很麻烦,不如用onmessage直接。

使用其他脚本文件

工作线程可以使用全局方法importScripts来加载和使用其他的域内脚本文件或者类库。例如下面都是合法的使用方式:

importScripts();

importScripts('foo.js');

importScripts('foo.js', 'bar.js');

导入以后,可以直接使用这些文件中的方法。看一个网上的小例子:

importScripts('math_utilities.js');

onmessage = function (event)

{

var first = event.data.first;

var second = event.data.second;

calculate(first,second);

};

function calculate(first,second) {

//do the calculation work

var common_divisor=divisor(first,second);

var common_multiple=multiple(first,second);

postMessage("Work done! " +

"The least common multiple is " + common_divisor +

" and the greatest common divisor is "+common_multiple);

}

网上也有网友想到了利用这里的importScripts方法解决资源预加载的问题(浏览器预先加载资源,而不会对资源进行解析和执行),道理也很简单。

线程嵌套

在工作线程中还可以在创建子线程,各种操作还是一样的。

同步问题

Worker没有锁的机制,多线程的同步问题只能靠代码来解决(比如定义信号变量)。

共享型SharedWebWorker

  共享型web worker主要适用于多连接并发的问题。因为要处理多连接,所以它的API与专用型worker稍微有点区别。除了这一点,共享型web worker和专用型worker一样,不能访问DOM,并且对窗体属性的访问也受到限制。共享型web worker也不能跨越通信。

  页面脚本可以与共享型web worker通信,然而,与专用型web worker(使用了一个隐式的端口通信)稍微有点不同的是,通信是显式的通过使用一个端口(port)对象并附加上一个消息事件处理程序来进行的。

  在收到web worker脚本的首个消息之后,共享型web worker把一个事件处理程序附加到激活的端口上。一般情况下,处理程序会运行自己的postMessage()方法来把一个消息返回给调用代码,接着端口的start()方法生成一个有效的消息进程。

看网上能找到的的唯一个例子:创建一个共享线程用于接收从不同连接发送过来的指令,然后实现自己的指令处理逻辑,指令处理完成后将结果返回到各个不同的连接用户。

HTML代码:

<script>

var worker = new SharedWorker('sharedworker.js');

var log = document.getElementByIdx_x_x_x_x('response_from_worker');

worker.port.addEventListener('message', function(e) {

//log the response data in web page

log.textContent =e.data;

}, false);

worker.port.start();

worker.port.postMessage('ping from user web page..');

//following method will send user input to sharedworker

function postMessageToSharedWorker(input)

{

//define a json object to construct the request

var instructions={instruction:input.value};

worker.port.postMessage(instructions);

}

</script>

脚本文件代码:

// 创建一个共享线程用于接收从不同连接发送过来的指令,指令处理完成后将结果返回到各个不同的连接用户。

var connect_number = 0;

onconnect = function(e) {

connect_number =connect_number+ 1;

//get the first port here

var port = e.ports[0];

port.postMessage('A new connection! The current connection number is '

+ connect_number);

port.onmessage = function(e) {

//get instructions from requester

var instruction=e.data.instruction;

var results=execute_instruction(instruction);

port.postMessage('Request: '+instruction+' Response '+results

+' from shared worker...');

};

};

function execute_instruction(instruction)

{

var result_value;

//implement your logic here

//execute the instruction...

return result_value;

}

在上面的共享线程例子中,在主页面即各个用户连接页面构造出一个共享线程对象,然后定义了一个方法 postMessageToSharedWorker 向共享线程发送来之用户的指令。同时,在共享线程的实现代码片段中定义 connect_number 用来记录连接到这个共享线程的总数。之后,用 onconnect 事件处理器接受来自不同用户的连接,解析它们传递过来的指令。最后,定义一个了方法 execute_instruction 用于执行用户的指令,指令执行完成后将结果返回给各个用户。

这里我们并没有跟前面的例子一样使用到了工作线程的 onmessage 事件处理器,而是使用了另外一种方式 addEventListener。实际上,前面已经说过,这两种的实现原理基本一致,只是在这里有些稍微的差别,如果使用到了 addEventListener 来接受来自共享线程的消息,那么就要先使用 worker.port.start() 方法来启动这个端口。之后就可以像工作线程的使用方式一样正常的接收和发送消息。

线程中能做的事:

1.能使用setTimeout(), clearTimeout(), setInterval(),clearInterval()等函数。

2.能使用navigator对象。

3.能使用XMLHttpRequest来发送请求。

4.可以在线程中使用Web Storage。

5.线程中可以用self获取本线程的作用域。

线程中不能做的事:

1.线程中是不能使用除navigator外的DOM/BOM对象,例如window,document(想要操作的话只能发送消息给worker创建者,通过回调函数操作)。

2.线程中不能使用主线程中的变量和函数。

3.线程中不能使用有"挂起"效果的操作命令,例如alert等。

4.线程中不能跨域加载JS。

线程也是需要消耗资源的,而且使用线程也会带来一定的复杂性,所以如果没有充足的理由来使用额外的线程的话,那么就不要用它。

本文分享自微信公众号 - 交互设计前端开发与后端程序设计(interaction_Designer),作者:json

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2015-09-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 用微妙动效改善用户体验的简单方法

    应用程序中的动效具有新的改良性的意义。跟过去的华丽、混乱的网站动画不同,新的动画干净、流畅,并且易于导航。忘掉你所知的GIF、令人不愉快的广告和Flash网站吧...

    前朝楚水
  • pc端与移动端的事件总结

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <style>...

    前朝楚水
  • JavaScript 常用方法总结

    经常使用的 JS 方法,今天记下,以便以后查询 /* 手机类型判断 */ var BrowserInfo = { userAgent: navigator.us...

    前朝楚水
  • JS数组&两值交换不使用第三变量

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

    奋飛
  • 腾讯云对象储存cos的配置方法简单说明

    COS是腾讯云提供的对象存储服务,功能非常强大,可以作为文件服务器,CDN加速,静态网页服务,还提供了图片的常用操作。相关API文档:https://cloud...

    主机优惠教程
  • 对象储存cos-腾讯云对象储存cos

    COS是腾讯云提供的对象存储服务,功能非常强大,可以作为文件服务器,CDN加速,静态网页服务,还提供了图片的常用操作。

    张天华
  • Ramda 函数库参考教程

    学习函数式编程的过程中,我接触到了 Ramda.js。 我发现,这是一个很重要的库,提供了许多有用的方法,每个 JavaScript 程序员都应该掌握这个工具。...

    ruanyf
  • C#3.0新增功能03 隐式类型本地变量

    从 Visual C# 3.0 开始,在方法范围内声明的变量可以具有隐式“类型”var。 隐式类型本地变量为强类型,就像用户已经自行声明该类型,但编译器决定类型...

    张传宁老师
  • 我是如何通过网络摄像头分析wifi密码的

    看到 exploit-db.com 中报了一个《Netwave IP Camera - Password Disclosure》漏洞, 这个漏洞包含了wifi密...

    周俊辉
  • Linux Shell 中的 ()、(())、[]、[[]]、{} 怎么用是不是还傻傻分不清?

    ①命令组。括号中的命令将会新开一个子shell顺序执行,所以括号中的变量不能够被脚本余下的部分使用。括号中多个命令之间用分号隔开,最后一个命令可以没有分号,各命...

    昱良

扫码关注云+社区

领取腾讯云代金券