任何一个软件下载安装成功之后,其实只是一堆的机器码,存在我们的电脑的硬盘当中,也就是我们所能看到的一堆的exe文件,当然,有的软件比较大,可能会附带的有一堆的dll文件。
我们有两种方式执行这个软件:
大部分的软件,比如QQ、飞秋、chrome浏览器,我们双击就可以执行运行起来。
有一部分的软件,是需要在命令行里面运行的,比如我们的node.js。
当一个软件被执行的时候,我们的操作系统就会创建一个对应的进程,可以这么理解,进程就是活的软件。
进程与线程
计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行,假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,单个CPU一次只能运行一个任务。进程就好比工厂的车间,它代表CPU所能处理的单个任务,任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。一个车间里,可以有很多工人,他们协同完成一个任务。线程就好比车间里的工人,一个进程可以包括多个线程。车间的空间是工人共享的,比如许多房间是每个工人都可以进出的,这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。可是,每间房间的大小不同,有些房间最多能容纳一个人,比如厕所,里面有人的时候,其他的人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这块内存。
一个防止他人进入的简单方法,就是门口加一把锁,先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去,这叫互斥锁。
为什么JavaScript是单线程的?
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊。
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
JavaScript是单线程的,虽然有worker、cluster等可以实现多线程,但是这些都是用来创建功能受限的线程,还是要受主线程的控制,所以,我们就认为JavaScript是单线程的。
另外,我们要明白,JavaScript是单线程的,不代表Node.js也是单线程的,Node.js运行了一个实例之后,是单进程多线程的,这里面,有一个线程负责V8引擎解析JavaScript,有的线程负责libuv,也就是我们理解的事件循环。
如何理解IO:
I代表的是input,输入:比如读一个文件、发起一个ajax请求数据
O代表的是output,输出:比如写文件
同步 vs 异步
像下面这种,不涉及到读写文件、网络请求的、定时器的,全部属于同步代码:
var a = 1;
var b = 2;
function fn(){}
像下面这种都是属于异步代码:
fs.readFile('./a.js',callback);
fs.writeFile('./b.js',callback);
包括我们在浏览器端看到的一堆的鼠标、键盘事件、ajax等。
Node.js里大部分的涉及异步的对象都是继承自event.eventEmitter事件对象
理解Node.js执行代码的机制
我们可以把Node.js进程想像成一个餐厅:
厨师代表的是JavaScript主线程,他不停的忙着把主线程上的同步代码执行下去,直到执行完为止。
//同步代码
var a = 1;
//同步代码
var b = 2;
//异步代码
fs.readFile('./a.js',function(err,data){
if(err)throw data;
});
//同步代码
var c = 3;
//异步代码
setTimeout(function(){
},200);
//同步代码
console.log(a + b + c);
//同步代码
function fn(){
console.log('abc');
}
//异步代码
fs.writeFile('./b.js',myData,function(err){
if(err)throw err;
});
比如像上面的这样的代码,从上往下开始执行,遇到同步代码,直接交给厨师去完成,但遇到了异步的代码的时候,这时候先不执行,而是类似来了新客人一样,交给了我们的服务员,服务员就把这个客人点的菜登记在册,相当于把这个异步代码看成了一个事件,记录到了我们的事件队列当中。
当差不多的时候,同步代码全部执行完了,这时候,主线程就空闲下来了,这时候,会启动我们的事件循环线程,这个事件循环线程就会开始去查看服务员登记的有哪些客人点了菜,然后就会按先后的优先顺序,开始一个客人一个客人的炒他们的菜,也就是执行我们的那些异步代码,因为有很多的客人,每个客人的要求不太一样,就像我们的Node.js代码,有各种各样的异步,比如定时器、读文件、取文件、网络请求,所以Node.js专门有一堆的线程池,处理这些异步代码。
当线程池里的某个线程负责的异步执行完成了之后,相当于这个事件完成了,就会把这个事件对应的回调函数往主线程后面进行排队。
主线程上一旦有了新的要执行的代码,厨师一看有事情要做了,就开始工作了。
如果主线程上执行的回调函数对应的代码,又产生了新的异步代码,那又会登记到事件循环当中。
直到某时刻,事件队列里所有的事件被执行完毕,线程池慢慢的被清空了。
node.js就会调用process.exit(),操作系统销毁当前的这个node.js进程。
理解到上面基本就够了,下面再深入一步:
我们到了学习网络编程那一块的时候,我们知道,我们知道一个服务器容器的时候,除非我们主动的使用CTRL + C把当前的这个进程中止掉,否则会一直的等待客户端的连接并根据相应的路由进行相应的处理。
这个又怎么理解呢?
我们回忆一下我们jQuery里面学的API,事件监听有两种方式:
- one 一次性监听
- on 无限监听
我们可以这样理解:大部分的文件操作、ajax之类的,其实只需要做一次就会结束,不会再有第二次,他们就相当于one类型的事件模型。
但是如果涉及到网络编程像socket,http之类的,则相当于on无限监听。