Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

node中的process模块 #37

Open
SunShinewyf opened this issue Dec 29, 2017 · 12 comments
Open

node中的process模块 #37

SunShinewyf opened this issue Dec 29, 2017 · 12 comments

Comments

@SunShinewyf
Copy link
Owner

process对象是一个global(全局变量),它对于Node.js应用程序始终是可用的,所以在使用时无需使用require()

对于process部分的API,这里不详细讲,具体可移步这里

node 中的 console.log

js中的console.lognode中的console.log还是有一些区别的。
js中的console.log在一些情况下执行时存在异步情况,根据《你不知道的JavaScript中卷》中的描述:

并没有什么规范或一组需求指定console.* 方法族如何工作——它们并不是JavaScript 正式
的一部分,而是由宿主环境(请参考本书的“类型和语法”部分)添加到JavaScript 中的。因此,不同的浏览器和JavaScript 环境可以按照自己的意愿来实现,有时候这会引起混淆。

尤其要提出的是,在某些条件下,某些浏览器的console.log(..) 并不会把传入的内容立即输出。出现这种情况的主要原因是,在许多程序(不只是JavaScript)中,I/O 是非常低速的阻塞部分。所以,(从页面/UI 的角度来说)浏览器在后台异步处理控制台I/O 能够提高性能,这时用户甚至可能根本意识不到其发生。

下面这种情景不是很常见,但也可能发生,从中(不是从代码本身而是从外部)可以观察到这种情况:
PS:可以试试这个

var a = {
    index: 1
};
// 然后
console.log( a ); // ??
// 再然后
a.index++;

我们通常认为恰好在执行到console.log(..) 语句的时候会看到a 对象的快照,打印出类
似于{ index: 1 } 这样的内容,然后在下一条语句a.index++ 执行时将其修改,这句的执
行会严格在a 的输出之后。

多数情况下,前述代码在开发者工具的控制台中输出的对象表示与期望是一致的。

但是,这段代码运行的时候,浏览器可能会认为需要把控制台I/O 延迟到后台,在这种情况下,等到浏览器控制台输出对象内容时,a.index++ 可能已经执行,因此会显示{ index: 2 }。

到底什么时候控制台I/O 会延迟,甚至是否能够被观察到,这都是游移不定的。

如果在调试的过程中遇到对象在console.log(..) 语句之后被修改,可你却看到了意料之外的结果,要意识到这可能是这种I/O 的异步化造成的。

可见,js中的console.log并不是严格同步或者异步,而是取决于执行环境和I/O之间的异步化。

node中,console.log的底层实现代码如下:

Console.prototype.log = function log(...args) {
  write(this._ignoreErrors,
        this._stdout,
        util.format.apply(null, args),
        this._stdoutErrorHandler,
        this[kGroupIndent]);
};

底层使用的是process.stdout.writenode中的console.log是同步还是异步的呢,也就是process.stdout.write是同步的还是异步的。官方文档给出的解释是:

写操作是否为同步,取决于连接的是什么流以及操作系统是 Windows 还是 POSIX :

  • Files: 同步 在 Windows 和 POSIX 下
  • TTYs (Terminals): 异步 在 Windows 下, 同步 在 POSIX 下
  • Pipes (and sockets): 同步 在 Windows 下, 异步 在 POSIX 下

process 的 child_process

child_processnode中一个比较重要的模块,众所周知,node有一个一直被人诟病的地方就是“单进程单线程”,但是有了child_process之后,node就可以实现在程序中直接创建子进程,除此之外,子进程和主进程之间还可以进行通信,这样就榨干了cpu的资源,使资源得到了充分地利用。

如何创建一个子进程,创建同步进程:

  • child_process.execFileSync(file[, args][, options])
  • child_process.execSync(command[, options])
  • child_process.spawnSync(command[, args][, options])

创建异步进程:

  • child_process.exec(command[, options][, callback])
  • child_process.execFile(file[, args][, options][, callback])
  • child_process.fork(modulePath[, args][, options])
  • child_process.spawn(command[, args][, options])

各个不同的方法之间的关系如下:
exec、execFile、fork都是通过spawn封装而成,由此可见,spawn是最基础的,它只能运行指定的程序,参数需要在列表中列出,但是exec在执行时则衍生出一个shell并在shell上运行。和exec类似的是execFile,但它执行命令,无需衍生出一个shell,所以execFileexec更加安全,也更高效。fork也是在spawn中封装出来的,专门用于衍生新的Node.js进程,跟 child_process.spawn() 一样返回一个 ChildProcess 对象。 返回的 ChildProcess 会有一个额外的内置的通信通道,它允许消息在父进程和子进程之间来回传递。

详细的可以参见官方文档

孤儿进程和僵尸进程

在unix/linux中,子进程是父进程创建的,子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束。 当一个 进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。

  • 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。init进程会循环wait()它的退出的子进程并进行善后工作,所以孤儿进程并不会带来什么实质性的危害。
  • 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。一旦有很多只处理少量任务的子进程完成任务后就退出,然后父进程又不管子进程的退出,然后就会产生很多的僵死进程,这样会对程序产生一定的危害。

node中 的 cluster

clusternode内置的一个模块,用于node多核处理,它的工作进程由child_process.fork()方法创建,因此它们可以使用IPC和父进程通信,从而使各进程交替处理连接服务。cluster中创建worker的源码如下:

function createWorkerProcess(id, env) {
  //省略一些代码

  return fork(cluster.settings.exec, cluster.settings.args, {
    env: workerEnv,
    silent: cluster.settings.silent,
    execArgv: execArgv,
    stdio: cluster.settings.stdio,
    gid: cluster.settings.gid,
    uid: cluster.settings.uid
  });
}

而且手动传了一些环境变量的参数值。如下是根据cluster.isMaster标识来fork子进程的代码:

if (cluster.isMaster) {                                    
  for (var i = 0; i < numCPUs; i++) {           
    cluster.fork();                            
  }  
  //master和worker之间的通信     
  cluster.on('fork', function (worker) {
    console.log('[master] ' + 'fork: worker' + worker.id);
  });
	
  cluster.on('online', function (worker) {
	 console.log('[master] ' + 'online: worker' + worker.id);
  });
		
  cluster.on('listening', function (worker, address) {
	 console.log('[master] ' + 'listening: worker' + worker.id + ',pid:' + worker.process.pid + ', Address:' + address.address + ":" + address.port);
  });
	
  cluster.on('disconnect', function (worker) {
    console.log('[master] ' + 'disconnect: worker' + worker.id);
  });
	
  cluster.on('exit', function (worker, code, signal) {
    console.log('[master] ' + 'exit worker' + worker.id + ' died');
  });                                     
                                            
} else {                                               
  http.createServer((req, res) => {            
    res.writeHead(200);                        
    res.end('hello world\n');                  
  }).listen(8000);                             
}                                             
                                               
console.log('hello');  

cluster模块支持两种连接分发模式(将新连接安排给某一工作进程处理)。

第一种方法(也是除Windows外所有平台的默认方法),是循环法。由主进程负责监听端口,接收新连接后再将连接循环分发给工作进程。在分发中使用了一些内置技巧防止工作进程任务过载。

第二种方法是,主进程创建监听socket后发送给感兴趣的工作进程,由工作进程负责直接接收连接。

理论上第二种方法应该是效率最佳的,但在实际情况下,由于操作系统调度机制的难以捉摸,会使分发变得不稳定。我们遇到过这种情况:8个进程中的2个,分担了70%的负载。

总结

这里只是集合一些知识点,结合饿了么前端面试整理了一些自己对这一块不太清楚的知识点,权当笔记用了,以后可以方便回顾~

参考文档:

@xtx1130
Copy link

xtx1130 commented Jan 2, 2018

感觉博主说的console这块有点问题啊,console是基于process.stdout.write,但是console.log(a),a这个参数是直接传进来的:代码链接
这这里是对args的处理,再看write:代码链接,在调用process.stdout.write之前已经拼接好string了,所以应该是console.log的args是同步的,只不过在输出的时候调用的process.stdout.write。
而write的异步问题,确实代码里面进行了兼容处理了:代码链接,通过event emitter的once方法对error进行监听,这样就相当于把write丢到了libuv的event loop中了,无论同步异步,只要触发了error,就会打断,而下面的catch,则是怕运行栈溢出,直接hold住uv_defalut_loop了,所以会catch一下,做下溢出判断。

@SunShinewyf
Copy link
Owner Author

但是wirte里面只是调用了stream.write(string, errorhandler);而且try里面只是捕获同步错误,对于你上面说的不论同步还是异步,都会走进catch有点不太理解,而且这个容错处理和stream.write是同步还是异步有什么关系呢

@xtx1130
Copy link

xtx1130 commented Jan 3, 2018

异步不会走到catch,我这里说的确实有问题。
你关联一下上下文,stream.write其实是process.stdout.write,而process.stdout官网api文档介绍了是一个socket流,因此会继承于event emitter,所以

stream.once('error', noop);

这个方法是存在的,而error事件是error handler,专门用来监听error的。
如果process.stdout是异步的,那么可以直接监听到process.stderr,然后通过

stream.write(string, errorhandler);

errorhandler来返回错误信息。
如果process.stdout是同步的且在执行的时候存在运行栈溢出的情况,这种时候通过监听'error'事件可以判断是否是运行栈溢出,因为stream.once是event emitter,所以回调noop会等到主loop执行完后执行。如果运行栈溢出,noop无法被触发,会抛错并走到catch流程中,而在catch中,如果是第一次运行栈溢出,会通过try来拿到运行栈溢出错误的message:

if (MAX_STACK_MESSAGE === undefined) {
      try {
        // eslint-disable-next-line no-unused-vars
        function a() { a(); }
      } catch (err) {
        MAX_STACK_MESSAGE = err.message;
      }
    }

之后,通过对比第一次try的e.message和运行栈溢出的错误信息来抛出错误:

if (e.message === MAX_STACK_MESSAGE && e.name === 'RangeError')
      throw e;

@SunShinewyf
Copy link
Owner Author

你说的对于write函数中对于错误处理这块我是很认同的,但对于错误捕获的部分和process.stdout.write是异步还是同步好像没有什么很直接的关联吧,还是说我没有完全get到你想要表达“我阐述的console这块有问题的点”....

@xtx1130
Copy link

xtx1130 commented Jan 3, 2018

……

但是,这段代码运行的时候,浏览器可能会认为需要把控制台I/O 延迟到后台,在这种情况下,等到浏览器控制台输出对象内容时,a.index++ 可能已经执行,因此会显示{ index: 2 }。

你说的这句话,应该是不会发生的,因为在执行这个函数console.log(a)的时候,a就已经固定了,引用之前我说过的那句话:

在调用process.stdout.write之前已经拼接好string了

只不过在输出到控制台的时候可能会在a.index++后面,因为输出是用的process.stdout.write。

@SunShinewyf
Copy link
Owner Author

SunShinewyf commented Jan 3, 2018

上面评论中你引用的那个描述:

但是,这段代码运行的时候,浏览器可能会认为需要把控制台I/O 延迟到后台,在这种情况下,等到浏览器控制台输出对象内容时,a.index++ 可能已经执行,因此会显示{ index: 2 }。

是针对js中的console.log来说的,并不是针对node中的console.log,只有node中的console.log才是用的process.stdout.write来进行输出

@xtx1130
Copy link

xtx1130 commented Jan 4, 2018

额,好吧,我读下来感觉你在说node。😓但是我在浏览器中也没遇到过这种情况。不过process.stdin/out底层调用的都是libuv,而chrome底层是开发人员二次开发的libuv2,理论上chrome的表现应该和node差不多,不过我也不十分确定。。。

@SunShinewyf
Copy link
Owner Author

@xtx1130 chrome中console.log的解释是直接摘自《你不知道的JavaScript》的。不过很感谢和你交流那么多,感觉你研究得很深~

@xtx1130
Copy link

xtx1130 commented Jan 4, 2018

@SunShinewyf 哦哦,YDK js 那本书是么,我还真没仔细看过。。。有时间摘出来翻翻。不过chrome源码确实没看过。

@SunShinewyf
Copy link
Owner Author

@xtx1130 链接 建议看看,还不错~

@xtx1130
Copy link

xtx1130 commented Jan 5, 2018

@SunShinewyf 哈哈,我之前star过一个YDK JS

@SunShinewyf
Copy link
Owner Author

应该是一样的,只是译本吧,哈哈

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants