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之六 缓存数据功能介绍
Jun 21 Javascript
Javascript 页面模板化很多人没有使用过的方法
Jun 05 Javascript
js通过更改按钮的显示样式实现按钮的滑动效果
Apr 23 Javascript
Hallo.js基于jQuery UI所见即所得的Web编辑器
Jan 26 Javascript
bootstrap fileinput完整实例分享
Nov 08 Javascript
javascript DOM的详解及实例代码
Mar 06 Javascript
JS验证全角与半角及相互转化的介绍
May 18 Javascript
vue生命周期和react生命周期对比【推荐】
Sep 19 Javascript
详解ES6中的 Set Map 数据结构学习总结
Nov 06 Javascript
JQuery获取元素尺寸、位置及页面滚动事件应用示例
May 14 jQuery
Bootstrap table 实现树形表格联动选中联动取消功能
Sep 30 Javascript
three.js显示中文字体与tween应用详析
Jan 04 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设计模式之单例模式实例分析
2015/02/25 PHP
PHP遍历数组的方法汇总
2015/04/30 PHP
php简单压缩css样式示例
2016/09/22 PHP
PHP面向对象程序设计之对象生成方法详解
2016/12/02 PHP
php提取微信账单的有效信息
2018/10/01 PHP
关于PHP虚拟主机概念及如何选择稳定的PHP虚拟主机
2018/11/20 PHP
PHP生成二维码与识别二维码的方法详解【附源码下载】
2019/03/07 PHP
jQuery实现 注册时选择阅读条款 左右移动
2013/04/11 Javascript
js设置cookie过期及清除浏览器对应名称的cookie
2013/10/24 Javascript
理解Javascript闭包
2013/11/01 Javascript
jQuery实现标签页效果实战(4)
2017/02/08 Javascript
jQuery plugin animsition使用小结
2017/09/14 jQuery
vue-router 路由基础的详解
2017/10/17 Javascript
基于打包工具Webpack进行项目开发实例
2018/05/29 Javascript
Vue.js 中 axios 跨域访问错误问题及解决方法
2018/11/21 Javascript
vue选项卡切换登录方式小案例
2019/09/27 Javascript
js实现橱窗展示效果
2020/01/11 Javascript
Javascript如何实现扩充基本类型
2020/08/26 Javascript
Python中用字符串调用函数或方法示例代码
2017/08/04 Python
django 发送手机验证码的示例代码
2018/04/25 Python
Python使用win32com模块实现数据库表结构自动生成word表格的方法
2018/07/17 Python
python3 实现一行输入,空格隔开的示例
2018/11/14 Python
Python实现密码薄文件读写操作
2019/12/16 Python
Pyecharts绘制全球流向图的示例代码
2020/01/08 Python
Docker部署Python爬虫项目的方法步骤
2020/01/19 Python
用python介绍4种常用的单链表翻转的方法小结
2020/02/24 Python
python使用gdal对shp读取,新建和更新的实例
2020/03/10 Python
Django中的模型类设计及展示示例详解
2020/05/29 Python
Python 操作 MySQL数据库
2020/09/18 Python
html5指南-4.使用Geolocation实现定位功能
2013/01/07 HTML / CSS
世界上最具创新性的增强型知名运动品牌:Proviz
2018/04/03 全球购物
英国礼品和生活方式品牌:Treat Republic
2020/11/21 全球购物
商业企业管理专业求职信
2014/07/10 职场文书
2014公司党员自我评价范文
2014/09/11 职场文书
竞聘开场白方式有哪些?
2019/08/28 职场文书
导游词之南京中山陵
2019/11/27 职场文书