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 相关文章推荐
图片Slider 带左右按钮的js示例
Aug 30 Javascript
jquery 日期控件datepicker属性详细解析
Nov 08 Javascript
用js来刷新当前页面保留参数的具体实现
Dec 23 Javascript
使用javascript实现有效时间的控制,并显示将要过期的时间
Jan 02 Javascript
javascript 终止函数执行操作
Feb 14 Javascript
iScroll中事件点击触发两次解决方案
Mar 11 Javascript
Yii2使用Bootbox插件实现自定义弹窗
Apr 02 Javascript
jQuery插件jRumble实现网页元素抖动
Jun 05 Javascript
AngularJS学习笔记之ng-options指令
Jun 16 Javascript
Bootstrap与Angularjs的模态框实例代码
Aug 03 Javascript
js删除对象/数组中null、undefined、空对象及空数组方法示例
Nov 14 Javascript
详解jenkins自动化部署vue
May 14 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控制网页过期时间的代码
2008/09/28 PHP
php 删除无限级目录与文件代码共享
2008/11/22 PHP
php error_log 函数的使用
2009/04/13 PHP
php通用防注入程序 推荐
2011/02/26 PHP
PHP的password_hash()使用实例
2014/03/17 PHP
Yii框架表单模型和验证用法
2016/05/20 PHP
php 生成加密公钥加密私钥实例详解
2017/06/16 PHP
Laravel接收前端ajax传来的数据的实例代码
2017/07/20 PHP
PHP面向对象五大原则之依赖倒置原则(DIP)详解
2018/04/08 PHP
javascript的函数、创建对象、封装、属性和方法、继承
2011/03/10 Javascript
基于jquery的监控数据是否发生改变
2011/04/11 Javascript
js判断元素是否隐藏的方法
2014/06/09 Javascript
JavaScript中的console.time()函数详细介绍
2014/12/29 Javascript
浅析Node.js中使用依赖注入的相关问题及解决方法
2015/06/24 Javascript
支持移动端原生js轮播图
2017/02/16 Javascript
详解Vue路由开启keep-alive时的注意点
2017/06/20 Javascript
JavaScript中最常用的10种代码简写技巧总结
2017/06/28 Javascript
通俗易懂地解释JS中的闭包
2017/10/23 Javascript
angularjs实现柱状图动态加载的示例
2017/12/11 Javascript
详解js的作用域、预解析机制
2018/02/05 Javascript
dts文件中删除一个node或属性的操作方法
2018/08/05 Javascript
mpvue项目中使用第三方UI组件库的方法
2018/09/30 Javascript
jQuery实现动态添加和删除input框代码实例
2019/03/29 jQuery
nest.js 使用express需要提供多个静态目录的操作方法
2019/10/24 Javascript
vue通过接口直接下载java生成好的Excel表格案例
2020/10/26 Javascript
python定时执行指定函数的方法
2015/05/27 Python
python3.5实现socket通讯示例(TCP)
2017/02/07 Python
解析python实现Lasso回归
2019/09/11 Python
Python递归调用实现数字累加的代码
2020/02/25 Python
Python bisect模块原理及常见实例
2020/06/17 Python
应届生新闻编辑求职信
2013/11/19 职场文书
医院总经理职责
2013/12/26 职场文书
婚前财产公证书
2014/04/10 职场文书
温馨提示标语
2014/06/26 职场文书
法院四风对照检查材料思想汇报
2014/10/06 职场文书
房屋认购协议书
2015/01/29 职场文书