layout | title | |
---|---|---|
default |
|
一般的话会使用单线程串行一次执行,还有多线程并行完成,但是单线程串行执行,很明显速度会很慢,性能会很差,而多线程的话,可能会早场死锁以及状态的同步问题。
于是Nodejs采用的单线程,异步IO,用单线程来避免死锁以及状态同步的问题,而异步的IO,则避免了阻塞。而正对无法利用多核CPU的问题,Node提供了子进程的方式来搞笑利用CPU和IO。
异步和非阻塞其实是两回事。
对于操作系统对内核的IO来说,只有阻塞与非阻塞。
阻塞就是就是简单的所有磁盘寻道,读取数据,复制数据之后才返回。
非阻塞IO则是系统发出查询的请求后,请求到达之后不带数据返回,然后通过轮询的方式查看什么时候结束了再来拿取数据。
这种轮询也经历了不断地进步,从不断的发送read;到后来select一次可以检查很多次,因为是数组;到后来的poll,以链表形式突破了长度限制;到后来的epoll,该方案是linux下效率最高的事件唤醒,因为他会休眠到结束,而不会一直轮询。还有kqueue,不过这个只在FressBSD系统下存在。
但是注意在轮询或者休眠的状态下实际上主线程是闲置的,是利用不足的。
Linux下有那种理想的异步IO方式,AIO,但是有缺陷,无法利用系统缓存。
其实现实中的异步IO是多线程的的,让部分线程来通过阻塞的或者非阻塞+轮询的形式来拿到数据,然后让一个线程进行计算处理,就实现了异步IO。
最终的结果就是nodejs通过libuv来运行在windows和linux系统上。
注意我们时常的说Node是单线程的,但是其实仅仅只是js执行在单线程中而已,node中,内部完成IO任务的其实都是另有线程池的。
除了IO之外,还有些异步的API,分别是setTimeout,setInterval,setImmediate和process.nextTick
一般浏览器端我们想要立即异步执行一个函数,就setTimeout(function(){},0)
就行了,但是定时器需要动用红黑树,创建定时器对象和迭代,浪费性能。
process.nextTick则是比较轻量的,直接将回调函数放入队列。
setImmediate也是类似的,但是这个的优先级比process.nextTick要低,并且如果同时写两个的话,执行了一个之后,会直接进行下一轮循环
,而不是继续执行。
- 同步式:一次只能处理一个请求,其余的都得等待。
- 每进程/每请求:每个请求一个进程,但是扩展性不强,因为系统资源只有那么多。
- 每线程/每请求:每个请求启动一个线程,尽管线程比较轻量,但是会占内存,导致服务器缓慢。
Node是通过事件驱动处理请求,不创建额外的对应线程。但是当大量请求来到的时候,究竟node发生了什么??tudo??
原来高阶函数就是将函数作为参数
或者将函数作为返回值
的函数。
就是说通过指定部分参数来产生一个新的定制函数的函数
- 异常处理
异步方法通常在第一个阶段提交请求后就立即返回了,因为异常不一定发生在这个阶段,try/catch没啥用。所以一般编写的异步函数的callback的第一个函数作为err对象,如果为null就代表没有错误,这其实是一种规范。
- 函数嵌套过深
当操作存在依赖关系时,我们可能会写成callback hell,当然这个世纪问题也有很多解决办法。
- 阻塞代码
没办法像sleep()这样的方法来将线程沉睡一下的,只能规划好代码结构调用setTimeout方法来做
- 多线程编程
node无法直接利用多核的好处,但是可以使用child_process来解决这个问题,深层次的是cluster模块
比如node自身提供的events模块,就是一个发布/订阅的模式。这个模式常常用来解耦业务,将不变的封装在组件内部,将容易变化,自定义的通过事件暴露给外部调用。
其实这种模式就是一种钩子机制,Node很多对象都是黑盒的,通过事件钩子,可以比较清晰的看出组件的功能。
比如HTTP请求,我们只需要关注error,data,end等等事件就可以了。
NODE对事件发布订阅做了一些额外的处理:
- 事件超过了10个监听器,会得到警报,因为设计者认为会出现内存泄露,调用emitter.setMaxListeners(0)可以将限制去掉
- 为了处理异常,EventEmitter对error事件进行特殊对待,如果监听了,就会交给监听器处理,否则作为异常抛出