Event Loop

关于线程和进程

  • 核心理论:
    • CPU: 计算机的核心是,它承担了所有的计算任务。它就像一座工厂,时刻在运行。

    • 单个CPU一次只能运行一个任务

    • 进程(Process):操作系统分配资源和调度任务的基本单位,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。

    • 线程(Thread):建立在进程上的一次程序运行单位, 多个线程组合成一个进程任务

    • 基本理论

      • 多进程形式允许多个任务同时运行
      • 多线程形式,允许单个任务分成不同的部分运行;
      • 提供协调机制(一个是互斥锁 Mutex,一个是信号量),一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源

多线程 跟 多进程的部分场景下的优劣选择

对比维度 多进程 多线程 总结
数据共享、同步 数据共享复杂,需要用IPC;数据是分开的,同步简单 因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂 各有优势
内存、CPU 占用内存多,切换复杂,CPU利用率低 占用内存少,切换简单,CPU利用率高 线程占优
创建销毁、切换 创建销毁、切换复杂,速度慢 创建销毁、切换简单,速度很快 线程占优
编程、调试 编程简单,调试简单 编程复杂,调试复杂 进程占优
可靠性 进程间不会互相影响 一个线程挂掉将导致整个进程挂掉 进程占优
分布式 适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单 适应于多核分布式 进程占优

Event Loop

  • 堆(heap)

    堆(heap)
    是指程序运行时申请的动态内存,在JS运行时用来存放对象。

  • 栈(stack)

    栈(stack)遵循的原则是“先进后出”

    栈内存1
        存放 JS中的基本数据类型 与 指向对象的地址
    栈内存2(简称:执行栈)
        执行 JS主线程
  • 队列(queue)

    队列(queue)遵循的原则是“先进先出”

      JS中除了主线程之外还存在一个“任务队列”(其实有两个,后面再详细说明)。
    

浏览器中的Event Loop

image.png

详细流程描述

  • 所有同步任务都在主线程上执行,形成一个执行栈和堆
  • 主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
  • 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,将队列中的事件放到执行栈中依次执行
  • 主线程从任务队列中读取事件,这个过程是循环不断的

case 如下:

例如利用setTimeout 实现的这个case

console.log(1);
console.log(2);
setTimeout(function(){
    console.log(3)
    setTimeout(function(){
        console.log(6);
    })
},0)
setTimeout(function(){
    console.log(4);
    setTimeout(function(){
        console.log(7);
    })
},0)
console.log(5)

代码中的setTimeout的时间给得0,相当于4ms,也有可能大于4ms(不重要)。我们要注意的是代码输出的顺序。我们把任务以其输出的数字命名。
先执行的一定是同步代码,先输出1,2,5,而3任务,4任务这时会依次进入“任务队列中”。同步代码执行完毕,队列中的3会进入执行栈执行,4到了队列的最前端,3执行完后,内部的setTimeout将6的任务放入队列尾部。开始执行4任务……
最终我们得到的输出为1,2,5,3,4,6,7。

处理步骤为:

  • 主线程执行,形成执行栈
  • 执行栈优先执行同步代码,优先输出1,2,5
  • 异步任务 推到主线程之外的任务队列(callback queue)
  • 同步代码执行完毕, 任务队列中的3会进入执行栈执行, 4到了队列的最前端
  • 3执行完后,内部的setTimeout将6的任务放入队列尾部。开始执行4任务
  • 依次执行.

Node 中的 Event Loop

术语阐述

  • 事件驱动(event-driven)

    是nodejs中的第二大特性。何为事件驱动呢?

    简单来说,就是通过监听事件的状态变化来做出相应的操作

    比如读取一个文件,文件读取完毕,或者文件读取错误,那么就触发对应的状态,然后调用对应的回掉函数来进行处理。

  • 线程驱动

    是当收到一个请求的时候,将会为该请求开一个新的线程来处理请求

    一般存在一个线程池,线程池中有空闲的线程,会从线程池中拿取线程来进行处理,如果线程池中没有空闲的线程,新来的请求将会进入队列排队,直到线程池中空闲线程

  • nodejs 单线程(single thread)

      nodejs是单线程运行的,通过一个事件循环(event-loop)来循环取出消息队列(event-queue)中的消息进行处理,处理过程基本上就是去调用该消息对应的回调函数。
    
      消息队列就是当一个事件状态发生变化时,就将一个消息压入队列中。
    

nodejs的时间驱动模型一般要注意下面几个点:

  1. 因为是单线程的,所以当顺序执行js文件中的代码的时候,事件循环是被暂停的。

  2. 当js文件执行完以后,事件循环开始运行,并从消息队列中取出消息,开始执行回调函数

  3. 因为是单线程的,所以当回调函数被执行的时候,事件循环是被暂停的

  4. 当涉及到I/O操作的时候,nodejs会开一个独立的线程来进行异步I/O操作,操作结束以后将消息压入消息队列。

实现异步IO

异步IO(asynchronous I/O)

首先来理解几个容易混淆的概念,阻塞IO(blocking I/O)和非阻塞IO(non-blocking I/O),同步IO(synchronous I/O)和异步IO(synchronous I/O)。

阻塞I/O 和 非阻塞I/O

简单来说,阻塞I/O

当用户发一个读取文件描述符的操作的时候,进程就会被阻塞,直到要读取的数据全部准备好返回给用户,这时候进程才会解除block的状态。

那非阻塞I/O呢,就与上面的情况相反

当用户发起一个读取文件描述符操作的时,函数立即返回,不作任何等待,进程继续执行。但是程序如何知道要读取的数据已经准备好了呢?最简单的方法就是轮询。

除此之外,还有一种叫做IO多路复用的模式,就是用一个阻塞函数同时监听多个文件描述符,当其中有一个文件描述符准备好了,就马上返回,在linux下,select,poll,epoll都提供了IO多路复用的功能。

同步I/O 和 异步I/O

那么同步I/O和异步I/O又有什么区别么?
是不是只要做到非阻塞IO就可以实现异步I/O呢?

其实不然。

同步I/O(synchronous I/O)做I/O operation的时候会将process阻塞,所以阻塞I/O,非阻塞I/O,IO多路复用I/O都是同步I/O。

异步I/O(asynchronous I/O)做I/O opertaion的时候将不会造成任何的阻塞。

非阻塞I/O都不阻塞了为什么不是异步I/O呢?其实当非阻塞I/O准备好数据以后还是要阻塞住进程去内核拿数据的。所以算不上异步I/O。

宏任务与微任务

任务队列中的所有任务都是会乖乖排队的吗?

答案是否定的,任务也是有区别的,总是有任务会有一些特权(比如插队),就是任务中的vip--微任务(micro-task),那些没有特权的--宏任务(macro-task)。

我们看一段代码:

console.log(1);

setTimeout(function(){
    console.log(2);
    Promise.resolve(1).then(function(){
        console.log('promise')
    })
})

setTimeout(function(){
    console.log(3);
})

按照“队列理论”,结果应该为1,2,3,promise。

可是实际结果事与愿违输出的是1,2,promise,3。

明明是3先进入的队列 ,为什么promise会排在前面输出?

这是因为promise有特权是微任务,当主线程任务执行完毕微任务会排在宏任务前面先去执行,不管是不是后来的。

换句话说,就是任务队列实际上有两个,一个是宏任务队列,一个是微任务队列,
当主线程执行完毕,如果微任务队列中有微任务,则会先进入执行栈,
当微任务队列没有任务时,才会执行宏任务的队列。

微任务,如下:

原生Promise(有些实现的promise将then方法放到了宏任务中)
Object.observe(已废弃)
MutationObserver
MessageChannel 等

宏任务, 如下:

setTimeout
setInterval
setImmediate
I/O 等

执行图示

   ┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<─────┤  connections, │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
   └───────────────────────┘

image.png

流程描述

  • js代码会交给 v8引擎 进行处理
  • 代码中可能会调用nodeApi,node会交给 libuv库 处理
  • libuv通过 阻塞i/o多线程 实现了异步io
  • 通过事件驱动的方式,将结果放到事件队列中,最终交给我们的应用。

process.nextTick

process.nextTick方法不在上面的事件环中,我们可以把它理解为微任务,
它的执行时机是当前"执行栈"的尾部

---- 下一次Event Loop(主线程读取"任务队列")之前

---- 触发回调函数。

也就是说,它指定的任务总是发生在所有异步任务之前。

setImmediate方法则是在当前"任务队列"的尾部添加事件,
也就是说,它指定的任务总是在下一次 Event Loop 时执行

上代码:

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

代码可以看出,不仅函数A比setTimeout指定的回调函数timeout先执行,而且函数B也比timeout先执行。

这说明,如果有多个process.nextTick语句(不管它们是否嵌套),将全部在当前"执行栈"执行。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335

推荐阅读更多精彩内容

  • 1 我同事的朋友,给我讲过一个人。那人是她的同学,她们在宿舍曾经聊一个问题,就是你有没有恨过一个人。 那个女孩说,...
    公子凉阅读 485评论 0 0
  • 正值午后时分,水泥路面似乎冒着热气,预报最高温度37度。 昕诚边驾车边听着轻松的音乐,行驶在去往公司的路上。 前方...
    田真十阅读 423评论 0 0
  • 前段时间回了一趟眉山,乡下的老房子已无人居住,回去也没有意义,于是我选择去大姨家看看她那一家子。大姨和大姨父在自家...
    灯下漫笔阅读 157评论 0 0
  • 为什么有些人明明年纪轻轻,考虑问题却总是细致周到? 为什么有些人经常休息玩耍,但却依然能把工作完成得很好? 刘未鹏...
    笑薇读书阅读 885评论 0 2
  • 梦是一生的梦 一生是梦的一生 我拾起梦的碎片 拼凑成天马行空 而天马行空是我的一生 一生是我最后的梦
    the之南阅读 217评论 0 0