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 相关文章推荐
JavaScript浏览器选项卡效果
Aug 25 Javascript
页面回到顶部的三种实现(锚标记,js)
Oct 01 Javascript
jquery delay()介绍及使用指南
Sep 02 Javascript
JavaScript常用验证函数实例汇总
Nov 25 Javascript
早该知道的7个JavaScript技巧
Jun 21 Javascript
微信小程序 自定义对话框实例详解
Jan 20 Javascript
Vue Ajax跨域请求实例详解
Jun 20 Javascript
详谈js模块化规范
Jul 07 Javascript
Vue2.0点击切换类名改变样式的方法
Aug 22 Javascript
Vue引用Swiper4插件无法重写分页器样式的解决方法
Sep 27 Javascript
JSON是什么?有哪些优点?JSON和XML的区别?
Apr 29 Javascript
JavaScript中的函数申明、函数表达式、箭头函数
Dec 06 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
DOTA2【瓜皮时刻】Vol.91 RTZ山史最惨“矿难”
2021/03/05 DOTA
PHP MYSQL简易交互式站点开发
2016/12/27 PHP
DWR实现模拟Google搜索效果实现原理及代码
2013/01/30 Javascript
js Dialog 去掉右上角的X关闭功能
2014/04/23 Javascript
原生js结合html5制作小飞龙的简易跳球
2015/03/30 Javascript
JavaScript实现的简单拖拽效果
2015/06/01 Javascript
js跨域请求数据的3种常用的方法
2015/12/01 Javascript
this,this,再次讨论javascript中的this,超全面(经典)
2016/01/05 Javascript
JS仿Base.js实现的继承示例
2017/04/07 Javascript
antd组件Upload实现自己上传的实现示例
2018/12/18 Javascript
jQuery - AJAX load() 实例用法详解
2019/08/27 jQuery
layui--js控制switch的切换方法
2019/09/03 Javascript
vue框架制作购物车小球动画效果实例代码
2019/09/26 Javascript
vue实现输入一位数字转汉字功能
2019/12/13 Javascript
jQuery 实现扁平式小清新导航
2020/07/07 jQuery
在项目vue中使用echarts的操作步骤
2020/09/07 Javascript
python使用urllib模块和pyquery实现阿里巴巴排名查询
2014/01/16 Python
python opencv实现旋转矩形框裁减功能
2018/07/25 Python
python 计算一个字符串中所有数字的和实例
2019/06/11 Python
python烟花效果的代码实例
2020/02/25 Python
python要安装在哪个盘
2020/06/15 Python
如何清空python的变量
2020/07/05 Python
Python txt文件常用读写操作代码实例
2020/08/03 Python
用CSS3写的模仿iPhone中的返回按钮
2015/04/04 HTML / CSS
孕妇内衣和胸罩:Cake Maternity
2018/07/16 全球购物
程序员跳槽必看面试题总结
2013/06/28 面试题
入党思想汇报
2014/01/05 职场文书
优秀毕业生事迹材料
2014/02/12 职场文书
犯错检讨书
2014/02/21 职场文书
优秀的应届生自荐信
2014/05/23 职场文书
教师培训简讯
2015/07/20 职场文书
致毕业季:你如何做好自己的职业生涯规划书?
2019/07/01 职场文书
python中requests库+xpath+lxml简单使用
2021/04/29 Python
修改MySQL的默认密码的四种小方法
2021/05/26 MySQL
OpenCV-Python实现油画效果的实例
2021/06/08 Python
MongoDB数据库部署环境准备及使用介绍
2022/03/21 MongoDB