javascript中的进程、线程和协程

这周一直在编前端构建的脚本,用到了多进程去解决一个效率问题。期间差了很多进程、线程、协程的资料,在这里记录回顾一下。

概念

关于进程、线程、协程的概念这里就不再赘述了,具体的可以参考wiki百科:

  • 进程

  • 线程

  • 协程

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

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

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

在一个进程中执行的程序,有时需要同时处理多个工作,这时我们可以创建多个线程,让每个线程处理一个工作。但是,进程只有一个。就好比一个人,你给他分配了多个工作,帮他把每个工作单独拉了一个列表,可还是他一个人干,他只能一会儿干干这一会儿干干那,来模拟多个工作同时进行的状态,这就是所谓的系统控制切换,系统不停的在多个线程间切换来达到并行的效果。你可能会说,那根一件一件干不是一样吗?没错,是一样的,在只有一个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之类的库做兼容……

更详细介绍请看阿阮的文章: 异步操作和Async函数

yield/Generator/GeneratorFunction

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

更详细介绍请看阿阮的文章: Generator 函数

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

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

work_fibo.js

var fibo = functionfibo(n){//定义算法
	return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
}
process.on('message', function(m){
//接收主进程发送过来的消息
	if(typeof m === 'object' && m.type === 'fibo'){
		var num = fibo(~~m.num);
		//计算jibo
		process.send({type: 'fibo',result:num})
		//计算完毕返回结果
	}
});
process.on('SIGHUP', function(){
	process.exit();//收到kill信息,进程退出
});

libuv

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

对c++不了解我就不瞎BB了,推荐两篇文章延伸阅读:

  • libuv多线程处理的简单示例
  • 利用libuv编写异步多线程的addon实例

tagg

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

还是那刚才的斐波那契数组计算为例:

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

最后结果:

not block
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实现了多进程打包的。

最后,祝大家新年快乐,1号胖三斤,3号金三胖~ ┑( ̄Д  ̄)┍

原文 

http://brooch.me/2016/12/30/process-thread-and-coroutine-in-javascript/

PS:如果您想和业内技术大牛交流的话,请加qq群(527933790)或者关注微信公众 号(AskHarries),谢谢!

转载请注明原文出处:Harries Blog™ » javascript中的进程、线程和协程

赞 (0)

分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址