进程的操作

进程的概念

进程(英语:Process)是计算机中已运行程序的实体。

node.js实际上是单进程的,自己编写的javascript代码运行在这个单进程,和浏览器模式一样,如果需要研究node.js运行原理,可以研究一下node.js的源代码。

虽然这样,I/O输入输出基本都是异步的,也可以说是多进程的,举例来说读取磁盘的文件就是单独一个进程负责的,这个读取过程不会影响我们编写的javascript程序的进程,当读到数据后,node.js内部会激发一个事件,把数据传输给我们编写的javascript程序。看下面代码:

var fs = require("fs");
fs.readFile("1.pdf",function(err,file){
    console.log("one")
});
console.log("two");

运行这个程序会先打印two,然后才打印one,这个并不奇怪,因为console.log("one")是在一个监听器体内,这个监听器必须等到另外一个进程执行完成后,然后node.js才会调用监听器,而当前我们自己编写的console.log("two")属于当前进程中的代码,会直接执行。

在node.js中,通过两个模块可以控制主进程和创建子进程等操作,这个两个模块是process和child_process。

创建和杀死进程

本节很简单的介绍一下创建与销毁,不会深入讲解,主要是有个大体理解。之后几节会详细深入的介绍具体操作进程的各个方法。

创建主进程

创建进程有很多方式方法,运行一个node.js程序时就已创建了一个进程了,也就是主进程。主进程通过global.process或process直接访问到。

创建子进程的几种方法

有时候可能需要在主进程中,运行其他node.js程序,也就是启动一个子进程,子进程的创建和操作依靠 child_process模块。看下面代码:

// main.js
var cdr = require("child_process");
cdr.fork(__dirname+"/child.js");

// child.js 子程序
console.log("子进程");

运行main.js 会打印出“子进程”。

创建node.js程序子进程,也可通过 child_process.exec 执行系统程序,比如windows的exe程序等,在下几节会介绍spawn和exec方法的差异,眼下认为功能都一样。看下面程序打开一个网页。

var cdr = require("child_process");
cdr.exec("start http://www.google.com")

运行该程序会通过默认浏览器打开google。

child_process.exec(command, callback)

command 执行命令,例如 ls -a

callback(error:Error,stdout:Buffer,stderr:Buffer)

return 返回子进程对象

var exec = require("child_process").exec;
var child = exec("dir",function(err,stdout,stderr){
    console.log(stdout);
});

例2

var exec = require("child_process").exec;
exec("start www.google.com"); // 默认浏览器打开网站
杀死主进程

通过process.exit(0)可以杀死主进程。process.abort()也可以。

杀死子进程

通过child_process.kill或child_process.disconnect()方法销毁子程序。看代码:

var cdr = require("child_process");
var cp = cdr.fork(__dirname+"/child");
setTimeout(function(){
  // cp.disconnect()
  cp.kill();
},3000);

3秒后杀死子进程cp。


进程间通信

主进程与子进程之间如何相互交流?这个问题不是一句话就能说清的,尤其是编程领域,所以还是老办法,做一个主进程和子进程交流的例子,再进行细化讲解。

// 主进程代码 main.js
var cdr = require("child_process");
var cp = cdr.fork(__dirname+"/child.js");
cp.on("message",function(msg){
    console.log(msg);
    cp.send("HI!");
})

// 子进程代码 child.js
process.on("message",function(m){
    console.log(m);
})
setInterval(function(){
    process.send("hello");
},2000);

运行main.js程序,终端会不断的打印:

hello
HI!
hello
HI!

这里要说明的是子进程和主进程必须都是node.js程序,也就是通过child_process.fork方法创建的子进程,才能进行有效的通信。

主进程和子程序都通过监听message得到主进程发送来的数据,并通过process.send(data)方法发送数据。

child.send(message, [sendHandle])

该方法可以发送数据信息到另一个进程,message是JSON对象,不能发送Function或者是本地对象。sendHandle[可选]数据,这个是数据可以是TCP服务器或Socket对象。

child.on("message",function(message,senHandle){})

这是监听器。

前面有发送JSON对象的例子,下面例子是发送一个http.Server对象。看如下代码:

// main.js 主进程程序
var cdr = require("child_process");
var http = require("http");
var server = http.createServer();
var cp = cdr.fork(__dirname+"/child");
server.listen(8080,function(){
    cp.send('server',server);
})

// child.js 子进程接收一个server对象
var Server = require("net").Server;
process.on("message",function(msg,server){
  console.log(server instanceof Server); // true
})

两个进程之间来回丢数据和接收数据,接收的数据可以是JSON对象,也可以是net.Server的对象,也可以是net.Socket对象。

process 表示自身进程。而 fork 出来的进程,相对于当前进程,fork出来的都是子进程,这句话要理解好。

所有process 都不认为自己是子进程,而只有别的进程通过 fork 它时,fork它的这个所谓主进程会当作它是子进程。

process.on("message") 接收到“主进程”发来的信息。

cp.on("message") 接受“子进程” 发来的信息。

每次fork 都会创建一个新的子进程。当主进程调用 cp.send 时,并不是所有子进程都能接收到,而只有当前fork出来的能接收到。

记住:js文件是死的,fork后才生成一个进程,多次fork会生成多个子进程,而相互之间毫无关系。


进程的属性与方法

node.js中进程操作的模块有process,其中process不需要require("process")导入。

其实process只叫主进程不准确,因为只要是采用node xxx.js 运行的程序都有一个process,包括前面说的用child_process.fork方法启动的程序。

先看process模块。

获得命令行参数

通过process.argv可以获得命令行参数。看例子:

// test.js
process.argv.forEach(function(val, index, array) {
   console.log(index + ': ' + val); 
});

然后运行 node test.js one two=three four

运行结果是:

0: node
1: /brighthas/test.js
2: one
3: two=three
4: four

第二个参数虽然是test.js 但会打印出完整的路径。

查看进程的相关信息

process.pid 得到进程的PID

process.execPath  得到node命令程序的完整路径

process.cwd()  得到当前的工作目录

process.env  得到环境变量(很多条信息)

process.version  得到 node 版本号

process.versions  得到node版本号和它依赖库的版本,比如可以得到openssl和v8的版本信息。

process.title  终端名称,可读写。

process.arch  得到系统架构名称,比如windows7 得到的结果是 ia32

process.platform 得到系统平台名称,比如windows7 得到的结果是 win32

process.memoryUsage() 得到当前进程的内存使用情况。

process.uptime() 进程从运行到现在的时间

process.nextTick(callback)

这个方法就是延迟执行callback,比如密集递归运输或大型运输,可以通过这个方法让其他代码程序有运行的机会,因为NODE.JS是单进程的,所以密集运算需要这个方法。

看下面递归代码:

function foo() {

   ......
   // 运算

   process.nextTick(foo);
}

foo();

通过这种方式,可以让其他程序代码有运行的机会。

但是这里有个问题,看下面代码:

process.nextTick(function foo() {
 process.nextTick(foo);
});

这样形成了无限深度的tick,通过process.maxTickDepth 可以设定最大tick深度,默认是1000。

process.hrtime() 精准测试运行间隔

这个方法的功能是第一次运行 var time1 = process.hrtime() , time1是个参考时间,格式是[秒,纳秒],然后在另外一行代码调用 var time2 = process.hrtime(time1) , time2 就是从参考时间time1到运行这段代码的时间间隔,time2的格式还是[秒,纳秒] 格式,也就是时间间隔是多少秒+多少纳秒。

一纳秒是十亿分之一秒。写一段代码,这段代码引用自node.js官网。

var time = process.hrtime();

setTimeout(function() {
  var diff = process.hrtime(time);

  console.log('耗时 %d 纳秒', diff[0] * 1e9 + diff[1]);

}, 1000);

终端打印出 

耗时 1016570258 纳秒

当然结果每台电脑各不相同。

标准输入输出

process.stderr 和 process.stdout 是输出流,是一个stream.Writable对象,参看(“流”一章)。

process.stdin 是输入流,是一个stream.Readable对象,参看(“流”一章)。

look做个例子,该例子监听终端输入的信息,然后通过输出流打印出去。

process.stdin.on("data",function(data){
   process.stdout.write(data + "\n");
})
进程事件

'exit' 进程退出事件。

yellowprocess跨平台的并且稳定的事件就只有 exit,还有两个事件这里不介绍了,因为不是跨平台的。