微信号:infoqchina

介绍:有内容的技术社区媒体

Node.js的线程和进程

2014-04-09 18:31 吴中骅

很多Node.js初学者都会有这样的疑惑,Node.js到底是单线程的还是多线程的?通过本章的学习,能够让读者较为清晰的理解Node.js对于单/多线程的关系和支持情况。同时本章还将列举一些让Node.js的web服务器线程阻塞的例子,最后会提供Node.js碰到这类cpu密集型问题的解决方案。


在学习本章之前,读者需要对Node.js有一个初步的认识,熟悉Node.js基本语法、cluster模块、child_process模块和express框架;接触过apache的http压力测试工具ab;了解一般web服务器对于静态文件的处理流程。


Node.js和PHP


早期有很多关于Node.js争论的焦点都在它的单线程模型方面,在由Jani Hartikainen写的一篇著名的文章《PHP优于Node.js的五大理由》中,更有一条矛头直接指向Node.js单线程脆弱的问题。


如果PHP代码损坏,不会拖垮整个服务器。PHP代码只运行在自己的进程范围中,当某个请求显示错误时,它只对特定的请求产生影响。而在Node.js环境中,所有的请求均在单一的进程服务中,当某个请求导致未知错误时,整个服务器都会受到影响。


Node.js和Apache+PHP还有一个非常不同的地方就是进程的运行时间长短,当然这一点也被此文作为一个PHP优于Node.js的理由来写了。


PHP进程短暂。在PHP中,每个进程对请求持续的时间很短暂,这就意味着你不必为资源配置和内存而担忧。而Node.js的进程需要运行很长一段时间,你需要小心并妥善管理好内存。比如,如果你忘记从全局数据中删除条目,这会轻易的导致内存泄露。


在这里我们并不想引起一次关于PHP和Node.js孰优孰劣的口水仗,PHP和Node.js各代表着一个互联网时代的开发语言,就如同我们讨论跑车和越野车谁更好一样,它们都有自己所擅长和适用的场景。我们可以通过下面这两张图深入理解一下PHP和Node.js对处理Http请求时的区别。


PHP的模型:



Node.js的模型:



所以你在编写Node.js代码时,要保持清醒的头脑,任何一个隐藏着的异常被触发后,都会将整个Node.js进程击溃。但是这样的特性也为我们编写代码带来便利,比如同样要实现一个简单的网站访问次数统计,Node.js只需要在内存里定义一个变量var count=0;,每次有用户请求过来执行count++;即可。但是对于PHP来说就需要使用第三方媒介来存储这个count值了,比如创建一个count.txt文件来保存网站的访问次数。


单线程的js


Google的V8 Javascript引擎已经在Chrome浏览器里证明了它的性能,所以Node.js的作者Ryan Dahl选择了v8作为Node.js的执行引擎,v8赋予Node.js高效性能的同时也注定了Node.js和大名鼎鼎的Nginx一样,都是以单线程为基础的,当然这也正是作者Ryan Dahl设计Node.js的初衷。


单线程的优缺点


Node.js的单线程具有它的优势,但也并非十全十美,在保持单线程模型的同时,它是如何保证非阻塞的呢?


高性能


首先,单线程避免了传统PHP那样频繁创建、切换线程的开销,使执行速度更加迅速。第二,资源占用小,如果有对Node.js的web服务器做过压力测试的朋友可能发现,Node.js在大负荷下对内存占用仍然很低,同样的负载PHP因为一个请求一个线程的模型,将会占用大量的物理内存,很可能会导致服务器因物理内存耗尽而频繁交换,失去响应。


线程安全


单线程的js还保证了绝对的线程安全,不用担心同一变量同时被多个线程进行读写而造成的程序崩溃。比如我们之前做的web访问统计,因为单线程的绝对线程安全,所以不可能存在同时对count变量进行读写的情况,我们的统计代码就算是成百的并发用户请求都不会出现问题,相较PHP的那种存文件记录访问,就会面临并发同时写文件的问题。线程安全的同时也解放了开发人员,免去了多线程编程中忘记对变量加锁或者解锁造成的悲剧。


单线程的异步和非阻塞


Node.js是单线程的,但是它如何做到I/O的异步和非阻塞的呢?其实Node.js在底层访问I/O还是多线程的,有兴趣的朋友可以翻看Node.js的fs模块的源码,里面会用到libuv来处理I/O,所以在我们看来Node.js的代码就是非阻塞和异步形式的。


阻塞/非阻塞与异步/同步是两个不同的概念,同步不代表阻塞,但是阻塞肯定就是同步了。


举个现实生活中的例子,我去食堂打饭,我选择了A套餐,然后工作人员帮我去配餐,如果我就站在旁边,等待工作人员给我配餐,这种情况就称之为同步;若工作人员帮我配餐的同时,排在我后面的人就开始点餐,这样整个食堂的点餐服务并没有因为我在等待A套餐而停止,这种情况就称之为非阻塞。这个例子就简单说明了同步但非阻塞的情况。


再如果我在等待配餐的时候去买饮料,等听到叫号再回去拿套餐,此时我的饮料也已经买好,这样我在等待配餐的同时还执行了买饮料的任务,叫号就等于执行了回调,就是异步非阻塞了。


阻塞的单线程


既然Node.js是单线程异步非阻塞的,是不是我们就可以高枕无忧了呢?


还是拿上面那个买套餐的例子,如果我在买饮料的时候,已经叫我的号让我去拿套餐,可是我等了好久才拿到饮料,所以我可能在大厅叫我的餐号之后很久才拿到A套餐,这也就是单线程的阻塞情况。


在浏览器中,js都是以单线程的方式运行的,所以我们不用担心js同时执行带来的冲突问题,这对于我们编码带来很多的便利。


但是对于在服务端执行的Node.js,它可能每秒有上百个请求需要处理,对于在浏览器端工作良好的单线程js是否也能同样在服务端表现良好呢?更多精彩内容,请点击“阅读原文”。


文章最后进行了总结:


单线程的Node.js给我们编码带来了太多的便利和乐趣,我们应该时刻保持清醒的头脑,在写Node.js代码中切不可与PHP混淆,任何一个隐藏的问题都可能击溃整个线上正在运行的Node.js程序。


单线程异步的Node.js不代表不会阻塞,在主线程做过多的任务可能会导致主线程的卡死,影响整个程序的性能,所以我们要非常小心的处理大量的循环,字符串拼接和浮点运算等cpu密集型任务,合理的利用各种技术把任务丢给子线程或子进程去完成,保持Node.js主线程的畅通。


线程/进程的使用并不是没有开销的,尽可能减少创建和销毁线程/进程的次数,可以提升我们系统整体的性能和出错的概率。


最后请不要一味的追求高性能和高并发,因为我们可能不需要系统具有那么大的吞吐率。高效,敏捷,低成本的开发才是项目所需要的,这也是为什么Node.js能够在众多开发语言中脱颖而出的关键。


***********************************

本文来自InfoQ微信公众账号:infoqchina

1、回复“今日新闻”,查看今天更新的新闻;

2、回复“今日英文”,查看今天英文站的更新;

3、回复“文章 +关键词”,搜索关键词相关内容;

4、回复“QCon”,了解QCon大会相关信息;

5、回复“活动”,了解最近InfoQ组织的线下沙龙;

6、回复“架构师”,获取《架构师》下载地址;

7、回复“投稿”,了解投稿和加入编辑团队的流程。

***********************************

 
InfoQ 更多文章 Facebook如何实现PB级别数据库自动化备份 学术派Google软件工程师Matt Welsh谈移动开发趋势 Spotify为什么要使用一些“无聊”的技术? 妹纸们放假了,汉纸们做啥? 大多数重构可以避免
猜您喜欢 如何为正在飞行的飞机更换引擎 什么样的公司值得加入? WebService自动化 -- 浅谈WSDL调用 封装之路(一) BaseApp NDK SO 库开发与使用中的 ABI 构架选择