简介我们知道事件循环是nodejs中事件处理的基础,初始化和回调事件主要运行在事件循环中。除了事件循环,在nodejs中还有一个WorkerPool来处理一些耗时的操作,比如I/O操作。nodejs高效运行的秘诀就是使用异步IO,让少量的线程可以处理大量的客户端请求。同时由于使用了少量的线程,所以我们在写nodejs程序的时候一定要非常小心。nodejs中有两种线程,事件循环和工作池。第一类线程是EventLoop,也可以称为主线程,第二类是一个WorkerPool中的n个Workers线程。如果这两个线程执行回调的时间过长,那么我们可以认为这两个线程被阻塞了。线程阻塞的第一个方面会影响程序的性能,因为有些线程被阻塞了,会导致系统资源的占用。因为总的资源是有限的,这会导致处理其他业务的资源变少,从而影响程序的整体性能。另一方面,如果经常出现线程阻塞的情况,则极有可能被恶意攻击者发起DOS攻击,导致正常业务无法进行。Nodejs使用事件驱动的框架。EventLoop主要用来处理注册各种事件的回调,同时也负责处理非阻塞的异步请求,比如网络I/O。libuv实现的WorkerPool主要对外暴露了提交任务的API,用于处理一些开销比较大的task任务。这些任务包括CPU密集型操作和一些阻塞IO操作。而nodejs本身就有很多模块使用了WorkerPool。例如IO密集型操作:DNS模块中的dns.lookup()、dns.lookupService()。除了fs.FSWatcher()和用于显式同步文件系统的API之外,大多数其他文件系统模块都使用WorkerPool。CPU密集型操作:加密模块:crypto.pbkdf2()、crypto.scrypt()、crypto.randomBytes()、crypto.randomFill()、crypto.generateKeyPair()。Zlib模块:除了显示同步API外,其他API使用workerpool。一般来说,这些是使用WorkerPool的模块。另外,你也可以使用nodejs的C++插件自行提交任务到WorkerPool。事件循环中的队列和workerpool在之前的文档中,我们讲到了在事件循环中使用队列来存储事件的回调。事实上,这种描述并不准确。事件循环实际上维护了一个文件描述符的集合。这些文件描述符使用操作系统内核的epoll(Linux)、kqueue(OSX)、事件端口(Solaris)或IOCP(Windows)来侦听事件。当操作系统检测到事件就绪时,事件循环会调用事件绑定的回调事件,最后执行回调。相反,workerpool真正保存的是要执行的任务队列,这些任务队列中的任务都是由每个worker执行的。当执行完成后,Woker会通知EventLoop任务已经执行完毕。阻塞事件循环因为nodejs中的线程是有限的,如果一个线程被阻塞了,可能会影响到整个应用的执行,所以我们在编程的过程中一定要慎重考虑事件循环和workerpool,避免阻塞。事件循环主要关注用户连接和响应用户请求。如果事件循环被阻塞,将无法及时响应用户请求。因为事件循环主要是执行回调,所以我们的回调执行时间一定要短。事件循环的时间复杂度时间复杂度一般用来判断一个算法的运行速度。这里我们也可以使用时间复杂度的概念来分析事件循环中的回调。如果所有回调中的时间复杂度是一个常量,那么我们可以保证所有回调都能公平执行。但是如果有些回调的时间复杂度发生了变化,那就需要慎重考虑了。app.get('/constant-time',(req,res)=>{res.sendStatus(200);});先来看一个常数时间复杂度的情况,在上面的例子中我们直接设置respose的状态,是一个常数时间的操作。app.get('/countToN',(req,res)=>{letn=req.query.n;//在轮到其他人之前进行n次迭代for(leti=0;i
