UDN-企业互联网技术人气社区

板块导航

浏览  : 709
回复  : 6

[原生js] JavaScript中的进程、线程和协程

[复制链接]
htmlman的头像 楼主
发表于 2017-1-10 15:43:15 | 显示全部楼层 |阅读模式
  这周一直在编前端构建的脚本,用到了多进程去解决一个效率问题。期间差了很多进程、线程、协程的资料,在这里记录回顾一下。

  简单概括一下它们间的区别

  就是相对线程和协程,进程更独立,有自己的内存空间,所以进程间通信比较困难。线程比进程轻量级,属于同一进程的多个线程间可以共享全部资源。协程与线程类似,不同点在于,线程由系统控制切换,协程是由用户控制切换。

  那么,控制切换,指的是控制什么的切换呢?

  在一个进程中执行的程序,有时需要同时处理多个工作,这时我们可以创建多个线程,让每个线程处理一个工作。但是,进程只有一个。就好比一个人,你给他分配了多个工作,帮他把每个工作单独拉了一个列表,可还是他一个人干,他只能一会儿干干这一会儿干干那,来模拟多个工作同时进行的状态,这就是所谓的系统控制切换,系统不停的在多个线程间切换来达到并行的效果。你可能会说,那根一件一件干不是一样吗?没错,是一样的,在只有一个cpu的电脑上,用不用多线程程序执行的时间是一样的。但是,如果这个人长了两个脑袋呢?那么他就能同时处理两件工作了。多核cpu就是那个长了好多个脑地的人……而协程的切换是要由用户手动来控制的,所以协程并适合并行计算,而更多的用来优化程序结构。

  js都支持吗?

  这要看js在什么环境运行。

  在浏览器中,可以通过webworkers创建进程,可以通过async/await,yield/Generator/GeneratorFunction实现协程,控制程序切换。

  在node中,除了可以使用上面浏览器中可以使用的方法,还可以通过cluster,child_process创建进程,通过libuv,tagg创建线程

  刚才提到的那些都是啥?怎么用?

  webworkers

  简单点儿说就是使用webworkers你可以在全新的环境中运行一个你指定的js文件。这个全新的环境是独立的,既一个全新的进程,有点儿像一个新iframe还没有window.top,window.parent属性,哈哈……

  webworkers创建的进程和主进程之间可以通过message事件传递消息,但是消息只能是字符串,所以想要传对象和数组就只能传json了……这也是他不方便的地方。

  具体使用方法可以看MDN上的文章:使用 Web Workers

  async/await

  async/await是es7中新加的两个关键字,async 可以声明一个异步函数,此函数需要返回一个 Promise 对象。await 可以等待一个 Promise 对象 resolve,并拿到结果。

  其实就是类似汇编的寄存器和跳转指令……呃,通俗的说就是可以根据状态跳转态另一个函数半中间。

  由于es7还未在各个环境实现,想要使用的话还的用一些babel-polyfill之类的库做兼容……

  yield/Generator/GeneratorFunction

  generator是es6中新增的函数,本质是可以将一个函数执行暂停,并保存上下文,再次调用时恢复当时的状态。但是用来解决协程切换的问题貌似有点儿滥用特性的感觉呢……

  cluster

  cluster是node官方提供的一个多进程模块,效果和C语言的fork函数类似,当前文件完全重新执行一遍,通过cluster.isMaster判断是不是主进程,在区分不同的操作。进程间通过事件回调来通信,NodeJS 0.6.x 以上的版本开始支持。

  示例代码就不放了,node官方文档上写的很详细: cluster

  child_process

  node自带的child_process模块里的fork函数可以实现类似浏览器里webworkers的效果,使用方法和webworker一毛一样,都是通过读取新文件开启新进程,通过message通信。

  具体介绍请看文档: child_process.fork(modulePath[, args][, options])

  官方文档没有示例,下面给出一个web服务接收参数计算斐波那契数组的例子:

  index.js
  1. var express = require('express');
  2. var fork = require('child_process').fork;
  3. var app = express();
  4. app.get('/', function(req, res){
  5.         var worker = fork('./work_fibo.js') //创建一个工作进程
  6.         worker.on('message', function(m){//接收工作进程计算结果
  7.                 if('object' === typeof m && m.type === 'fibo'){
  8.                         worker.kill();//发送杀死进程的信号
  9.                         res.send(m.result.toString());//将结果返回客户端
  10.                 }
  11.         });
  12.         worker.send({type:'fibo',num:~~req.query.n || 1});//发送给工作进程计算fibo的数量
  13. });
  14. app.listen(8124);
复制代码

  work_fibo.js
  1. var fibo = functionfibo(n){//定义算法
  2.         return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
  3. }
  4. process.on('message', function(m){
  5. //接收主进程发送过来的消息
  6.         if(typeof m === 'object' && m.type === 'fibo'){
  7.                 var num = fibo(~~m.num);
  8.                 //计算jibo
  9.                 process.send({type: 'fibo',result:num})
  10.                 //计算完毕返回结果
  11.         }
  12. });
  13. process.on('SIGHUP', function(){
  14.         process.exit();//收到kill信息,进程退出
  15. });
复制代码

  libuv

  libuv是node底层实现使用的c++库……呃,所以如果你想使用这个库来实现多线程,那么你就得编写c++的代码了,不得不说,要想真正理解程序的本质,不多掌握几门语言真是不行啊……

  tagg

  tagg(Threads a gogo for Node.js)是Jorge Chamorro Bieling开发的一个node包。使用c语言phread库实现的多线程。

  还是那刚才的斐波那契数组计算为例:
  1. var Threads = require('threads_a_gogo');//加载tagg包
  2. functionfibo(n){//定义斐波那契数组计算函数
  3.         return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
  4. }
  5. var t = Threads.create().eval(fibo);
  6. t.eval('fibo(35)', function(err, result){//将fibo(35)丢入子线程运行
  7.         if (err) throw err; //线程创建失败
  8.         console.log('fibo(35)=' + result);//打印fibo执行35次的结果
  9. });
  10. console.log('not block');//打印信息了,表示没有阻塞
复制代码

  最后结果:
  1. not block
  2. fibo(35)=14930352
复制代码

  我们可以看到执行效果与webworker类似,不同的是通信使用了异步回调的方式。

  值得一提的是tagg包目前只能在linux下安装运行,这里再推荐一个tagg2包,是跨平台的。

  这里需要重点提一下的是,不论tagg还是tagg2包都是利用phtread库和v8的v8::Isolate Class类来实现js多线程功能的。

  Isolate代表着一个独立的v8引擎实例,v8的Isolate拥有完全分开的状态,在一个Isolate实例中的对象不能够在另外一个Isolate实例中使用。嵌入式开发者可以在其他线程创建一些额外的Isolate实例并行运行。在任何时刻,一个Isolate实例只能够被一个线程进行访问,可以利用加锁/解锁进行同步操作。

  换而言之,我们在进行v8的嵌入式开发时,无法在多线程中访问js变量,这条规则将直接导致我们之前的tagg2里面线程执行的函数无法使用Node.js的核心API,比如fs,crypto等模块。

  延伸阅读:

  tagg

  tagg2

  总结

  经过以上的学习,我们大概应该了解到进程、线程、协程的使用场景了,进程、线程适合用来处理计算密集型操作,协程适合用来优化代码结构,解决回调函数嵌套问题。线程比进程更轻,更节省资源,但是由于上面提到的线程问题,针对一些可以使用js原生的大量计算或循环还可以用用,涉及到使用nodejs核心api的操作,就要用进程解决了。

  p.s. 我的问题

  我在工作中使用的是fis配合grunt调用打包。由于要同时打包多个项目,grunt和fis都会定义全局变量,各个模块之间的配置可能会相互影响,各个模块在打包过程中又没有相互的通信,同时为了提高效率,非常时候适合使用多进程的方式来运行脚本。所以用cluster实现了多进程打包的。

相关帖子

发表于 2017-1-10 15:43:47 | 显示全部楼层
占坑编辑ing
使用道具 举报

回复

发表于 2017-1-10 15:43:47 | 显示全部楼层
个人觉得js是一种解释性语言,它提供了一个非常方便的开发过程,不需要编译,js与HTML标识结合在一起,从而方便用户的使用操作。
使用道具 举报

回复

发表于 2017-1-10 15:43:47 | 显示全部楼层
jq现在应该算是最火的js框架类了吧?确实很强大extjs基于Yahoo UI的扩展,界面布局和效果方面很给力,但就是有点复杂
使用道具 举报

回复

发表于 2017-1-10 15:43:48 | 显示全部楼层
前排支持下
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部