一,进程和线程简介
进程是资源分配的最小单位,线程是CPU调度的最小单位
进程:
进程是一个有独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,一般定义是:进程由程序,数据集合和进程控制块三部分组成。(进程是资源分配的最小单位)。
线程:
在早期的操作系统中并没有线程的概念,随着计算机的发展,为了应对进程之间的切换带来的巨大开销,就发明了线程,线程是程序执行中一个单一的顺序控制流程,一个进程可以有一个或多个线程,各个线程之间共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)。
二,为什么JavaScript是单线程?
JavaScript语言的一大特点就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
早期作为一个简单的浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。引入多线程会增加程序的复杂度,想象一下写个下拉菜单,还要考虑(Mutex)或者"信号量"(Semaphore)用来保证多个线程不会互相冲突。估计大部分前端都会瑟瑟发抖。
HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。这个本质上还是JavaScript式的单线程。大家不要慌,估计短期内,JavaScript 不会出现传统的多线程编程。
三,任务队列
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。对于很慢的IO设备输入输出(比如对磁盘和网络的操作),在等待IO设备返回结果时,CPU是闲着的
。这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
这里产生了两个概念:阻塞的同步任务(synchronous)和非阻塞的异步任务(asynchronous)。
1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
2)主线程之外,还存在一个"任务队列"(task queue)。异步任务不进入主线程、而进入任务队列,只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。"回调函数"(callback)就是那些会被主线程挂起来的代码。
4)主线程不断重复上面的第三步。
四,process.nextTick和setImmediate。
它们可以帮助我们加深对"任务队列"的理解。process.nextTick方法可以在当前"执行栈"的尾部——下一次Event Loop(主线程读取"任务队列")之前——触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。setImmediate方法则是在当前"任务队列"的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行,这与setTimeout(fn, 0)很像。请看下面的例子(via StackOverflow)。
process.nextTick(function A() { console.log(1); process.nextTick(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log('TIMEOUT FIRED'); }, 0) // 1 // 2 // TIMEOUT FIRED
上面代码中,由于process.nextTick方法指定的回调函数,总是在当前"执行栈"的尾部触发,所以不仅函数A比setTimeout指定的回调函数timeout先执行,而且函数B也比timeout先执行。这说明,如果有多个process.nextTick语句(不管它们是否嵌套),将全部在当前"执行栈"执行。
setImmediate(function (){ setImmediate(function A() { console.log(1); setImmediate(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log('TIMEOUT FIRED'); }, 0); }); // 1 // TIMEOUT FIRED // 2
上面代码中,setImmediate和setTimeout被封装在一个setImmediate里面,它的运行结果总是1--TIMEOUT FIRED--2,这时函数A一定在timeout前面触发。至于2排在TIMEOUT FIRED的后面(即函数B在timeout后面触发),是因为setImmediate总是将事件注册到下一轮Event Loop,所以函数A和timeout是在同一轮Loop执行,而函数B在下一轮Loop执行。
参考:
http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
https://www.ruanyifeng.com/blog/2014/10/event-loop.html