JavaScript中的Web worker多线程API研究


Posted in Javascript onDecember 06, 2014

HTML5支持了Web Worker这样的API,允许网页在安全的情况下执行多线程代码。不过Web Worker实际上受到很多限制,因为它无法真正意义上共享内存数据,只能通过消息来做状态通知,所以甚至不能称之为真正意义上的“多线程”。

Web Worker的接口使用起来很不方便,它基本上自带一个sandbox,在沙箱中跑一个独立的js文件,通过 postMessage和 onMessge来和主线程通信:

var worker = new Worker("my.js");

var bundle = {message:'Hello world', id:1};

worker.postMessage(bundle); //postMessage可以传一个可序列化的对象过去

worker.onmessage = function(evt){

    console.log(evt.data);    //比较worker中传回来的对象和主线程中的对象

    console.log(bundle);  //{message:'Hello world', id:1}

}
//in my.js

onmessage = function(evt){

    var data = evt.data;

    data.id++;

    postMessage(data); //{message:'Hello world', id:2}

}

得到的结果可以发现,线程中得到的data的id增加了,但是传回来之后,并没有改变主线程的bundle中的id,因此,线程中传递的对象实际上copy了一份,这样的话,线程并没有共享数据,避免了读写冲突,所以是安全的。保证线程安全的代价就是限制了在线程中操作主线程对象的能力。

这样一个有限的多线程机制使用起来是很不方便的,我们当然希望Worker能够支持让代码看起来具有同时操作多线程的能力,例如,支持看起来像下面这个样子的代码:

var worker = new ThreadWorker(bundle /*shared obj*/);
worker.run(function(bundle){

    //do sth in worker thread...

    this.runOnUiThread(function(bundle /*shared obj*/){

        //do sth in main ui thread...

    });

    //...

});

这段代码里面,我们启动一个worker之后,能够让任意代码跑在worker中,并且当需要操作ui线程(比如读写dom)时,可以通过this.runOnUiThread回到主线程执行。

那么如何实现这个机制呢? 看下面的代码:

function WorkerThread(sharedObj){

    this._worker = new Worker("thread.js");

    this._completes = {};

    this._task_id = 0;

    this.sharedObj = sharedObj;
    var self = this;

    this._worker.onmessage = function(evt){

        var ret = evt.data;

        if(ret.__UI_TASK__){

            //run on ui task

            var fn = (new Function("return "+ret.__UI_TASK__))();

            fn(ret.sharedObj);

        }else{

            self.sharedObj = ret.sharedObj;

            self._completes[ret.taskId](ret);

        }

    }

}
WorkerThread.prototype.run = function(task, complete){

    var _task = {__THREAD_TASK__:task.toString(), sharedObj: this.sharedObj, taskId: this._task_id};

    this._completes[this._task_id++] = complete;

    this._worker.postMessage(_task);

}

上面这段代码定义了一个ThreadWorker对象,这个对象创建了一个运行thread.js的Web Worker,保存了共享对象SharedObj,并且对thread.js发回的消息进行处理。

如果thread.js中传回了一个UI_TASK消息,那么运行这个消息传过来的function,否则执行run的complete回调 我们看看thread.js是怎么写的:

onmessage = function(evt){

    var data = evt.data;
    if(data && data.__THREAD_TASK__){

        var task = data.__THREAD_TASK__;

        try{

            var fn = (new Function("return "+task))();
            var ctx = {

                threadSignal: true,

                sleep: function(interval){

                    ctx.threadSignal = false;

                    setTimeout(_run, interval);

                },

                runOnUiThread: function(task){

                    postMessage({__UI_TASK__:task.toString(), sharedObj:data.sharedObj});

                }

            }
            function _run(){

                ctx.threadSignal = true;

                var ret = fn.call(ctx, data.sharedObj);

                postMessage({error:null, returnValue:ret, __THREAD_TASK__:task, sharedObj:data.sharedObj, taskId: data.taskId});

            }
            _run(0);
        }catch(ex){

            postMessage({error:ex.toString() , returnValue:null, sharedObj: data.sharedObj});

        }

    }

}

可以看到,thread.js接收ui线程传过来的消息,其中最重要的是THREAD_TASK,这是ui线程传过来的需要worker线程执行的“任务”,由于function是不可序列化的,因此传递的是字符串,worker线程通过解析字符串成function来执行主线程提交的任务(注意在任务中将共享对象sharedObj传入),执行完成后将返回结果通过message传给ui线程。我们仔细看一下除了返回值returnValue以外,共享对象sharedObj也会被传回,传回时,由于worker线程和ui线程并不共享对象,因此我们人为通过赋值的方式同步两边的对象(这样是否线程安全?为什么?)

可以看到整个过程其实并不复杂,这么实现之后,这个ThreadWorker可以有以下两种用法:

var t1 = new WorkerThread({i: 100} /*shared obj*/);
        setInterval(function(){

            t1.run(function(sharedObj){

                    return sharedObj.i++;

                },

                function(r){

                    console.log("t1>" + r.returnValue + ":" + r.error);

                }

            );

        }, 500);

var t2 = new WorkerThread({i: 50});
        t2.run(function(sharedObj){   

            while(this.threadSignal){

                sharedObj.i++;
                this.runOnUiThread(function(sharedObj){

                    W("body ul").appendChild("<li>"+sharedObj.i+"</li>");

                });
                this.sleep(500);

            }

            return sharedObj.i;

        }, function(r){

            console.log("t2>" + r.returnValue + ":" + r.error);

        });

这样的用法从形式和语义上来说都让代码具有良好的结构,灵活性和可维护性。

好了,关于Web Worker的用法探讨就介绍到这里,有兴趣的同学可以去看一下这个项目:https://github.com/akira-cn/WorkerThread.js (由于Worker需要用服务器测试,我特意在项目中放了一个山寨的httpd.js,是个非常简陋的http服务的js,直接用node就可以跑起来)。

Javascript 相关文章推荐
jQuery数组处理代码详解(含实例演示)
Feb 03 Javascript
cookie在javascript中的使用技巧以及隐私在服务器端的设置
Dec 03 Javascript
无缝滚动js代码通俗易懂(自写)
Jun 19 Javascript
jQuery选择器全集详解
Nov 24 Javascript
node.js中的url.parse方法使用说明
Dec 10 Javascript
JavaScript随机打乱数组顺序之随机洗牌算法
Aug 02 Javascript
AngularJS全局scope与Isolate scope通信用法示例
Nov 22 Javascript
Vue.js第二天学习笔记(vue-router)
Dec 01 Javascript
微信小程序日期选择器实例代码
Jul 18 Javascript
react-native使用leanclound消息推送的方法
Aug 06 Javascript
JavaScript实现答题评分功能页面
Jun 24 Javascript
React中的Context应用场景分析
Jun 11 Javascript
JavaScript实现的一个日期格式化函数分享
Dec 06 #Javascript
JavaScript实现twitter puddles算法实例
Dec 06 #Javascript
JavaScript实现的一个计算数字步数的算法分享
Dec 06 #Javascript
angularjs中的e2e测试实例
Dec 06 #Javascript
angularjs中的单元测试实例
Dec 06 #Javascript
angularjs指令中的compile与link函数详解
Dec 06 #Javascript
angularjs的一些优化小技巧
Dec 06 #Javascript
You might like
php实现的MySQL通用查询程序
2007/03/11 PHP
备份mysql数据库的php代码(一个表一个文件)
2010/05/28 PHP
PHP 下载文件时自动添加bom头的方法实例
2014/01/10 PHP
Javascript var变量隐式声明方法
2009/10/19 Javascript
基于Unit PNG Fix.js有时候在ie6下不正常的解决办法
2013/06/26 Javascript
jquery转盘抽奖功能实现
2015/11/13 Javascript
详解jquery事件delegate()的使用方法
2016/01/25 Javascript
学习使用jquery iScroll.js移动端滚动条插件
2020/03/24 Javascript
JavaScript中Promise的使用详解
2017/02/26 Javascript
JavaScript创建对象_动力节点Java学院整理
2017/06/27 Javascript
vue.js 双层嵌套for遍历的方法详解, 类似php foreach()
2018/09/07 Javascript
优雅的将ElementUI表格变身成树形表格的方法步骤
2019/04/11 Javascript
vue路由切换之淡入淡出的简单实现
2019/10/31 Javascript
vue实现跳转接口push 转场动画示例
2019/11/01 Javascript
Node.js API详解之 console模块用法详解
2020/05/12 Javascript
el-table表头根据内容自适应完美解决表头错位和固定列错位
2021/01/07 Javascript
[01:36]DOTA2完美大师赛趣味视频之与队友相处的十万个技巧
2017/11/19 DOTA
Python模块学习 datetime介绍
2012/08/27 Python
python使用Berkeley DB数据库实例
2014/09/26 Python
Python实现文件内容批量追加的方法示例
2017/08/29 Python
浅析python中numpy包中的argsort函数的使用
2018/08/30 Python
Python3爬虫爬取百姓网列表并保存为json功能示例【基于request、lxml和json模块】
2018/12/05 Python
Python爬虫实战之12306抢票开源
2019/01/24 Python
Django ValuesQuerySet转json方式
2020/03/16 Python
使用TensorBoard进行超参数优化的实现
2020/07/06 Python
IE8下CSS3选择器nth-child() 不兼容问题的解决方法
2016/11/16 HTML / CSS
基于HTML5 audio元素播放声音jQuery小插件
2011/05/11 HTML / CSS
购买澳大利亚最好的服装和内衣在线:BONDS
2016/10/14 全球购物
美国宠物商店:Wag.com
2016/10/25 全球购物
Brora官网:英国领先的羊绒服装品牌
2019/08/28 全球购物
adidas瑞典官方网站:购买阿迪达斯鞋子和运动服
2019/12/11 全球购物
文秘专业应届生求职信范文
2013/11/14 职场文书
机关节能减排实施方案
2014/03/17 职场文书
我的中国梦演讲稿400字
2014/08/19 职场文书
交通事故一次性赔偿协议书范本
2014/11/02 职场文书
python神经网络学习 使用Keras进行回归运算
2022/05/04 Python