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 相关文章推荐
Add a Table to a Word Document
Jun 15 Javascript
js中将URL中的参数提取出来作为对象的实现代码
Aug 16 Javascript
javascript基础知识大全 便于大家学习,也便于我自己查看
Aug 17 Javascript
从数据结构的角度分析 for each in 比 for in 快的多
Jul 07 Javascript
事件委托与阻止冒泡阻止其父元素事件触发
Sep 02 Javascript
js计算德州扑克牌面值的方法
Mar 04 Javascript
JavaScript将字符串转换为整数的方法
Apr 14 Javascript
jquery超简单实现手风琴效果的方法
Jun 05 Javascript
JavaScript实现数据类型的相互转换
Mar 06 Javascript
Node.js connect ECONNREFUSED错误解决办法
Sep 15 Javascript
vue的toast弹窗组件实例详解
May 14 Javascript
JS实现鼠标拖拽盒子移动及右键点击盒子消失效果示例
Jan 29 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
ThinkPHP结合ajax、Mysql实现的客户端通信功能代码示例
2014/06/23 PHP
PHP callback函数使用方法和注意事项
2015/01/23 PHP
通过chrome浏览器控制台(Console)进行PHP Debug的方法
2016/10/19 PHP
关于PHP中字符串与多进制转换函数的实例代码
2016/11/03 PHP
PHP 返回数组后处理方法(开户成功后弹窗提示)
2017/07/03 PHP
php微信公众号开发之校园图书馆
2018/10/20 PHP
jquery 屏蔽一个区域内的所有元素,禁止输入
2009/10/22 Javascript
javascript 鼠标悬浮图片显示原图 移出鼠标后原图消失(多图)
2009/12/28 Javascript
图片上传判断及预览脚本的效果实例
2013/08/07 Javascript
jquery ajax,ashx,json的用法总结
2014/02/12 Javascript
angularjs指令中的compile与link函数详解
2014/12/06 Javascript
使用AngularJS创建单页应用的编程指引
2015/06/19 Javascript
一些实用性较高的js方法
2016/04/19 Javascript
新入门node.js必须要知道的概念(必看篇)
2016/08/10 Javascript
用vue封装插件并发布到npm的方法步骤
2017/10/18 Javascript
JavaScript实现浅拷贝与深拷贝的方法分析
2018/07/05 Javascript
vue自定义移动端touch事件之点击、滑动、长按事件
2018/07/10 Javascript
JavaScript设计模式之职责链模式应用示例
2018/08/07 Javascript
浅谈Vue 性能优化之深挖数组
2018/12/11 Javascript
Vue 表情包输入组件的实现代码
2019/01/21 Javascript
移动端自适应flexible.js的使用方法(不用三大框架,仅写一个单html页面使用)推荐
2019/04/02 Javascript
Vue Components 数字键盘的实现
2019/09/18 Javascript
JS数组方法concat()用法实例分析
2020/01/18 Javascript
JavaScript中clientWidth,offsetWidth,scrollWidth的区别
2021/01/25 Javascript
Python实现微信公众平台自定义菜单实例
2015/03/20 Python
python集合类型用法分析
2015/04/08 Python
python链接oracle数据库以及数据库的增删改查实例
2018/01/30 Python
python验证码识别教程之利用投影法、连通域法分割图片
2018/06/04 Python
python实现自动发送邮件
2018/06/20 Python
Python3读写Excel文件(使用xlrd,xlsxwriter,openpyxl3种方式读写实例与优劣)
2020/02/13 Python
解决html5中video标签无法播放mp4问题的办法
2017/05/07 HTML / CSS
幼儿园教师备课制度
2014/01/12 职场文书
初中教师业务学习材料
2014/05/12 职场文书
大学生党员个人剖析材料
2014/10/08 职场文书
全陪导游词开场白
2015/05/29 职场文书
vue+elementUI实现表格列的显示与隐藏
2022/04/13 Vue.js